1 /*
  2  * ***** BEGIN LICENSE BLOCK *****
  3  * 
  4  * Zimbra Collaboration Suite Web Client
  5  * Copyright (C) 2012 VMware, Inc.
  6  * 
  7  * The contents of this file are subject to the Zimbra Public License
  8  * Version 1.3 ("License"); you may not use this file except in
  9  * compliance with the License.  You may obtain a copy of the License at
 10  * http://www.zimbra.com/license.
 11  * 
 12  * Software distributed under the License is distributed on an "AS IS"
 13  * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.
 14  * 
 15  * ***** END LICENSE BLOCK *****
 16  */
 17 // FILE IS GENERATED BY COMBINING THE SOURCES IN THE "classes" DIRECTORY SO DON'T MODIFY THIS FILE DIRECTLY
 18 (function(win) {
 19 	var whiteSpaceRe = /^\s*|\s*$/g,
 20 		undef, isRegExpBroken = 'B'.replace(/A(.)|B/, '$1') === '$1';
 21 
 22 	var tinymce = {
 23 		majorVersion : '3',
 24 
 25 		minorVersion : '5.4.1',
 26 
 27 		releaseDate : '2012-06-24',
 28 
 29 		_init : function() {
 30 			var t = this, d = document, na = navigator, ua = na.userAgent, i, nl, n, base, p, v;
 31 
 32 			t.isOpera = win.opera && opera.buildNumber;
 33 
 34 			t.isWebKit = /WebKit/.test(ua);
 35 
 36 			t.isIE = !t.isWebKit && !t.isOpera && (/MSIE/gi).test(ua) && (/Explorer/gi).test(na.appName);
 37 
 38 			t.isIE6 = t.isIE && /MSIE [56]/.test(ua);
 39 
 40 			t.isIE7 = t.isIE && /MSIE [7]/.test(ua);
 41 
 42 			t.isIE8 = t.isIE && /MSIE [8]/.test(ua);
 43 
 44 			t.isIE9 = t.isIE && /MSIE [9]/.test(ua);
 45 
 46 			t.isGecko = !t.isWebKit && /Gecko/.test(ua);
 47 
 48 			t.isMac = ua.indexOf('Mac') != -1;
 49 
 50 			t.isAir = /adobeair/i.test(ua);
 51 
 52 			t.isIDevice = /(iPad|iPhone)/.test(ua);
 53 			
 54 			t.isIOS5 = t.isIDevice && ua.match(/AppleWebKit\/(\d*)/)[1]>=534;
 55 
 56 			// TinyMCE .NET webcontrol might be setting the values for TinyMCE
 57 			if (win.tinyMCEPreInit) {
 58 				t.suffix = tinyMCEPreInit.suffix;
 59 				t.baseURL = tinyMCEPreInit.base;
 60 				t.query = tinyMCEPreInit.query;
 61 				return;
 62 			}
 63 
 64 			// Get suffix and base
 65 			t.suffix = '';
 66 
 67 			// If base element found, add that infront of baseURL
 68 			nl = d.getElementsByTagName('base');
 69 			for (i=0; i<nl.length; i++) {
 70 				v = nl[i].href;
 71 				if (v) {
 72 					// Host only value like http://site.com or http://site.com:8008
 73 					if (/^https?:\/\/[^\/]+$/.test(v))
 74 						v += '/';
 75 
 76 					base = v ? v.match(/.*\//)[0] : ''; // Get only directory
 77 				}
 78 			}
 79 
 80 			function getBase(n) {
 81 				if (n.src && /tiny_mce(|_gzip|_jquery|_prototype|_full)(_dev|_src)?.js/.test(n.src)) {
 82 					if (/_(src|dev)\.js/g.test(n.src))
 83 						t.suffix = '_src';
 84 
 85 					if ((p = n.src.indexOf('?')) != -1)
 86 						t.query = n.src.substring(p + 1);
 87 
 88 					t.baseURL = n.src.substring(0, n.src.lastIndexOf('/'));
 89 
 90 					// If path to script is relative and a base href was found add that one infront
 91 					// the src property will always be an absolute one on non IE browsers and IE 8
 92 					// so this logic will basically only be executed on older IE versions
 93 					if (base && t.baseURL.indexOf('://') == -1 && t.baseURL.indexOf('/') !== 0)
 94 						t.baseURL = base + t.baseURL;
 95 
 96 					return t.baseURL;
 97 				}
 98 
 99 				return null;
100 			};
101 
102 			// Check document
103 			nl = d.getElementsByTagName('script');
104 			for (i=0; i<nl.length; i++) {
105 				if (getBase(nl[i]))
106 					return;
107 			}
108 
109 			// Check head
110 			n = d.getElementsByTagName('head')[0];
111 			if (n) {
112 				nl = n.getElementsByTagName('script');
113 				for (i=0; i<nl.length; i++) {
114 					if (getBase(nl[i]))
115 						return;
116 				}
117 			}
118 
119 			return;
120 		},
121 
122 		is : function(o, t) {
123 			if (!t)
124 				return o !== undef;
125 
126 			if (t == 'array' && (o.hasOwnProperty && o instanceof Array))
127 				return true;
128 
129 			return typeof(o) == t;
130 		},
131 
132 		makeMap : function(items, delim, map) {
133 			var i;
134 
135 			items = items || [];
136 			delim = delim || ',';
137 
138 			if (typeof(items) == "string")
139 				items = items.split(delim);
140 
141 			map = map || {};
142 
143 			i = items.length;
144 			while (i--)
145 				map[items[i]] = {};
146 
147 			return map;
148 		},
149 
150 		each : function(o, cb, s) {
151 			var n, l;
152 
153 			if (!o)
154 				return 0;
155 
156 			s = s || o;
157 
158 			if (o.length !== undef) {
159 				// Indexed arrays, needed for Safari
160 				for (n=0, l = o.length; n < l; n++) {
161 					if (cb.call(s, o[n], n, o) === false)
162 						return 0;
163 				}
164 			} else {
165 				// Hashtables
166 				for (n in o) {
167 					if (o.hasOwnProperty(n)) {
168 						if (cb.call(s, o[n], n, o) === false)
169 							return 0;
170 					}
171 				}
172 			}
173 
174 			return 1;
175 		},
176 
177 
178 		trim : function(s) {
179 			return (s ? '' + s : '').replace(whiteSpaceRe, '');
180 		},
181 
182 		create : function(s, p, root) {
183 			var t = this, sp, ns, cn, scn, c, de = 0;
184 
185 			// Parse : <prefix> <class>:<super class>
186 			s = /^((static) )?([\w.]+)(:([\w.]+))?/.exec(s);
187 			cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name
188 
189 			// Create namespace for new class
190 			ns = t.createNS(s[3].replace(/\.\w+$/, ''), root);
191 
192 			// Class already exists
193 			if (ns[cn])
194 				return;
195 
196 			// Make pure static class
197 			if (s[2] == 'static') {
198 				ns[cn] = p;
199 
200 				if (this.onCreate)
201 					this.onCreate(s[2], s[3], ns[cn]);
202 
203 				return;
204 			}
205 
206 			// Create default constructor
207 			if (!p[cn]) {
208 				p[cn] = function() {};
209 				de = 1;
210 			}
211 
212 			// Add constructor and methods
213 			ns[cn] = p[cn];
214 			t.extend(ns[cn].prototype, p);
215 
216 			// Extend
217 			if (s[5]) {
218 				sp = t.resolve(s[5]).prototype;
219 				scn = s[5].match(/\.(\w+)$/i)[1]; // Class name
220 
221 				// Extend constructor
222 				c = ns[cn];
223 				if (de) {
224 					// Add passthrough constructor
225 					ns[cn] = function() {
226 						return sp[scn].apply(this, arguments);
227 					};
228 				} else {
229 					// Add inherit constructor
230 					ns[cn] = function() {
231 						this.parent = sp[scn];
232 						return c.apply(this, arguments);
233 					};
234 				}
235 				ns[cn].prototype[cn] = ns[cn];
236 
237 				// Add super methods
238 				t.each(sp, function(f, n) {
239 					ns[cn].prototype[n] = sp[n];
240 				});
241 
242 				// Add overridden methods
243 				t.each(p, function(f, n) {
244 					// Extend methods if needed
245 					if (sp[n]) {
246 						ns[cn].prototype[n] = function() {
247 							this.parent = sp[n];
248 							return f.apply(this, arguments);
249 						};
250 					} else {
251 						if (n != cn)
252 							ns[cn].prototype[n] = f;
253 					}
254 				});
255 			}
256 
257 			// Add static methods
258 			t.each(p['static'], function(f, n) {
259 				ns[cn][n] = f;
260 			});
261 
262 			if (this.onCreate)
263 				this.onCreate(s[2], s[3], ns[cn].prototype);
264 		},
265 
266 		walk : function(o, f, n, s) {
267 			s = s || this;
268 
269 			if (o) {
270 				if (n)
271 					o = o[n];
272 
273 				tinymce.each(o, function(o, i) {
274 					if (f.call(s, o, i, n) === false)
275 						return false;
276 
277 					tinymce.walk(o, f, n, s);
278 				});
279 			}
280 		},
281 
282 		createNS : function(n, o) {
283 			var i, v;
284 
285 			o = o || win;
286 
287 			n = n.split('.');
288 			for (i=0; i<n.length; i++) {
289 				v = n[i];
290 
291 				if (!o[v])
292 					o[v] = {};
293 
294 				o = o[v];
295 			}
296 
297 			return o;
298 		},
299 
300 		resolve : function(n, o) {
301 			var i, l;
302 
303 			o = o || win;
304 
305 			n = n.split('.');
306 			for (i = 0, l = n.length; i < l; i++) {
307 				o = o[n[i]];
308 
309 				if (!o)
310 					break;
311 			}
312 
313 			return o;
314 		},
315 
316 		addUnload : function(f, s) {
317 			var t = this, unload;
318 
319 			unload = function() {
320 				var li = t.unloads, o, n;
321 
322 				if (li) {
323 					// Call unload handlers
324 					for (n in li) {
325 						o = li[n];
326 
327 						if (o && o.func)
328 							o.func.call(o.scope, 1); // Send in one arg to distinct unload and user destroy
329 					}
330 
331 					// Detach unload function
332 					if (win.detachEvent) {
333 						win.detachEvent('onbeforeunload', fakeUnload);
334 						win.detachEvent('onunload', unload);
335 					} else if (win.removeEventListener)
336 						win.removeEventListener('unload', unload, false);
337 
338 					// Destroy references
339 					t.unloads = o = li = w = unload = 0;
340 
341 					// Run garbarge collector on IE
342 					if (win.CollectGarbage)
343 						CollectGarbage();
344 				}
345 			};
346 
347 			function fakeUnload() {
348 				var d = document;
349 
350 				function stop() {
351 					// Prevent memory leak
352 					d.detachEvent('onstop', stop);
353 
354 					// Call unload handler
355 					if (unload)
356 						unload();
357 
358 					d = 0;
359 				};
360 
361 				// Is there things still loading, then do some magic
362 				if (d.readyState == 'interactive') {
363 					// Fire unload when the currently loading page is stopped
364 					if (d)
365 						d.attachEvent('onstop', stop);
366 
367 					// Remove onstop listener after a while to prevent the unload function
368 					// to execute if the user presses cancel in an onbeforeunload
369 					// confirm dialog and then presses the browser stop button
370 					win.setTimeout(function() {
371 						if (d)
372 							d.detachEvent('onstop', stop);
373 					}, 0);
374 				}
375 			};
376 
377 			f = {func : f, scope : s || this};
378 
379 			if (!t.unloads) {
380 				// Attach unload handler
381 				if (win.attachEvent) {
382 					win.attachEvent('onunload', unload);
383 					win.attachEvent('onbeforeunload', fakeUnload);
384 				} else if (win.addEventListener)
385 					win.addEventListener('unload', unload, false);
386 
387 				// Setup initial unload handler array
388 				t.unloads = [f];
389 			} else
390 				t.unloads.push(f);
391 
392 			return f;
393 		},
394 
395 		removeUnload : function(f) {
396 			var u = this.unloads, r = null;
397 
398 			tinymce.each(u, function(o, i) {
399 				if (o && o.func == f) {
400 					u.splice(i, 1);
401 					r = f;
402 					return false;
403 				}
404 			});
405 
406 			return r;
407 		},
408 
409 		explode : function(s, d) {
410 			if (!s || tinymce.is(s, 'array')) {
411 				return s;
412 			}
413 
414 			return tinymce.map(s.split(d || ','), tinymce.trim);
415 		},
416 
417 		_addVer : function(u) {
418 			var v;
419 
420 			if (!this.query)
421 				return u;
422 
423 			v = (u.indexOf('?') == -1 ? '?' : '&') + this.query;
424 
425 			if (u.indexOf('#') == -1)
426 				return u + v;
427 
428 			return u.replace('#', v + '#');
429 		},
430 
431 		// Fix function for IE 9 where regexps isn't working correctly
432 		// Todo: remove me once MS fixes the bug
433 		_replace : function(find, replace, str) {
434 			// On IE9 we have to fake $x replacement
435 			if (isRegExpBroken) {
436 				return str.replace(find, function() {
437 					var val = replace, args = arguments, i;
438 
439 					for (i = 0; i < args.length - 2; i++) {
440 						if (args[i] === undef) {
441 							val = val.replace(new RegExp('\\$' + i, 'g'), '');
442 						} else {
443 							val = val.replace(new RegExp('\\$' + i, 'g'), args[i]);
444 						}
445 					}
446 
447 					return val;
448 				});
449 			}
450 
451 			return str.replace(find, replace);
452 		}
453 
454 		};
455 
456 	// Initialize the API
457 	tinymce._init();
458 
459 	// Expose tinymce namespace to the global namespace (window)
460 	win.tinymce = win.tinyMCE = tinymce;
461 
462 	// Describe the different namespaces
463 
464 	})(window);
465 
466 
467 (function($, tinymce) {
468 	var is = tinymce.is, attrRegExp = /^(href|src|style)$/i, undef;
469 
470 	// jQuery is undefined
471 	if (!$ && window.console) {
472 		return console.log("Load jQuery first!");
473 	}
474 
475 	// Stick jQuery into the tinymce namespace
476 	tinymce.$ = $;
477 
478 	// Setup adapter
479 	tinymce.adapter = {
480 		patchEditor : function(editor) {
481 			var fn = $.fn;
482 
483 			// Adapt the css function to make sure that the data-mce-style
484 			// attribute gets updated with the new style information
485 			function css(name, value) {
486 				var self = this;
487 
488 				// Remove data-mce-style when set operation occurs
489 				if (value)
490 					self.removeAttr('data-mce-style');
491 
492 				return fn.css.apply(self, arguments);
493 			};
494 
495 			// Apapt the attr function to make sure that it uses the data-mce- prefixed variants
496 			function attr(name, value) {
497 				var self = this;
498 
499 				// Update/retrive data-mce- attribute variants
500 				if (attrRegExp.test(name)) {
501 					if (value !== undef) {
502 						// Use TinyMCE behavior when setting the specifc attributes
503 						self.each(function(i, node) {
504 							editor.dom.setAttrib(node, name, value);
505 						});
506 
507 						return self;
508 					} else
509 						return self.attr('data-mce-' + name);
510 				}
511 
512 				// Default behavior
513 				return fn.attr.apply(self, arguments);
514 			};
515 
516 			// Patch various jQuery functions to handle tinymce specific attribute and content behavior
517 			// we don't patch the jQuery.fn directly since it will most likely break compatibility
518 			// with other jQuery logic on the page. Only instances created by TinyMCE should be patched.
519 			function patch(jq) {
520 				// Patch some functions, only patch the object once
521 				if (jq.css !== css) {
522 					// Patch css/attr to use the data-mce- prefixed attribute variants
523 					jq.css = css;
524 					jq.attr = attr;
525 
526 					jq.tinymce = editor;
527 
528 					// Each pushed jQuery instance needs to be patched
529 					// as well for example when traversing the DOM
530 					jq.pushStack = function() {
531 						return patch(fn.pushStack.apply(this, arguments));
532 					};
533 				}
534 
535 				return jq;
536 			};
537 
538 			// Add a $ function on each editor instance this one is scoped for the editor document object
539 			// this way you can do chaining like this tinymce.get(0).$('p').append('text').css('color', 'red');
540 			editor.$ = function(selector, scope) {
541 				var doc = editor.getDoc();
542 
543 				return patch($(selector || doc, doc || scope));
544 			};
545 		}
546 	};
547 
548 	// Patch in core NS functions
549 	tinymce.extend = $.extend;
550 	tinymce.extend(tinymce, {
551 		map : $.map,
552 		grep : function(a, f) {return $.grep(a, f || function(){return 1;});},
553 		inArray : function(a, v) {return $.inArray(v, a || []);}
554 
555 		/* Didn't iterate stylesheets
556 		each : function(o, cb, s) {
557 			if (!o)
558 				return 0;
559 
560 			var r = 1;
561 
562 			$.each(o, function(nr, el){
563 				if (cb.call(s, el, nr, o) === false) {
564 					r = 0;
565 					return false;
566 				}
567 			});
568 
569 			return r;
570 		}*/
571 	});
572 
573 	// Patch in functions in various clases
574 	// Add a "#ifndefjquery" statement around each core API function you add below
575 	var patches = {
576 		'tinymce.dom.DOMUtils' : {
577 			/*
578 			addClass : function(e, c) {
579 				if (is(e, 'array') && is(e[0], 'string'))
580 					e = e.join(',#');
581 				return (e && $(is(e, 'string') ? '#' + e : e)
582 					.addClass(c)
583 					.attr('class')) || false;
584 			},
585 
586 			hasClass : function(n, c) {
587 				return $(is(n, 'string') ? '#' + n : n).hasClass(c);
588 			},
589 
590 			removeClass : function(e, c) {
591 				if (!e)
592 					return false;
593 
594 				var r = [];
595 
596 				$(is(e, 'string') ? '#' + e : e)
597 					.removeClass(c)
598 					.each(function(){
599 						r.push(this.className);
600 					});
601 
602 				return r.length == 1 ? r[0] : r;
603 			},
604 			*/
605 
606 			select : function(pattern, scope) {
607 				var t = this;
608 
609 				return $.find(pattern, t.get(scope) || t.get(t.settings.root_element) || t.doc, []);
610 			},
611 
612 			is : function(n, patt) {
613 				return $(this.get(n)).is(patt);
614 			}
615 
616 			/*
617 			show : function(e) {
618 				if (is(e, 'array') && is(e[0], 'string'))
619 					e = e.join(',#');
620 
621 				$(is(e, 'string') ? '#' + e : e).css('display', 'block');
622 			},
623 
624 			hide : function(e) {
625 				if (is(e, 'array') && is(e[0], 'string'))
626 					e = e.join(',#');
627 
628 				$(is(e, 'string') ? '#' + e : e).css('display', 'none');
629 			},
630 
631 			isHidden : function(e) {
632 				return $(is(e, 'string') ? '#' + e : e).is(':hidden');
633 			},
634 
635 			insertAfter : function(n, e) {
636 				return $(is(e, 'string') ? '#' + e : e).after(n);
637 			},
638 
639 			replace : function(o, n, k) {
640 				n = $(is(n, 'string') ? '#' + n : n);
641 
642 				if (k)
643 					n.children().appendTo(o);
644 
645 				n.replaceWith(o);
646 			},
647 
648 			setStyle : function(n, na, v) {
649 				if (is(n, 'array') && is(n[0], 'string'))
650 					n = n.join(',#');
651 
652 				$(is(n, 'string') ? '#' + n : n).css(na, v);
653 			},
654 
655 			getStyle : function(n, na, c) {
656 				return $(is(n, 'string') ? '#' + n : n).css(na);
657 			},
658 
659 			setStyles : function(e, o) {
660 				if (is(e, 'array') && is(e[0], 'string'))
661 					e = e.join(',#');
662 				$(is(e, 'string') ? '#' + e : e).css(o);
663 			},
664 
665 			setAttrib : function(e, n, v) {
666 				var t = this, s = t.settings;
667 
668 				if (is(e, 'array') && is(e[0], 'string'))
669 					e = e.join(',#');
670 
671 				e = $(is(e, 'string') ? '#' + e : e);
672 
673 				switch (n) {
674 					case "style":
675 						e.each(function(i, v){
676 							if (s.keep_values)
677 								$(v).attr('data-mce-style', v);
678 
679 							v.style.cssText = v;
680 						});
681 						break;
682 
683 					case "class":
684 						e.each(function(){
685 							this.className = v;
686 						});
687 						break;
688 
689 					case "src":
690 					case "href":
691 						e.each(function(i, v){
692 							if (s.keep_values) {
693 								if (s.url_converter)
694 									v = s.url_converter.call(s.url_converter_scope || t, v, n, v);
695 
696 								t.setAttrib(v, 'data-mce-' + n, v);
697 							}
698 						});
699 
700 						break;
701 				}
702 
703 				if (v !== null && v.length !== 0)
704 					e.attr(n, '' + v);
705 				else
706 					e.removeAttr(n);
707 			},
708 
709 			setAttribs : function(e, o) {
710 				var t = this;
711 
712 				$.each(o, function(n, v){
713 					t.setAttrib(e,n,v);
714 				});
715 			}
716 			*/
717 		}
718 
719 /*
720 		'tinymce.dom.Event' : {
721 			add : function (o, n, f, s) {
722 				var lo, cb;
723 
724 				cb = function(e) {
725 					e.target = e.target || this;
726 					f.call(s || this, e);
727 				};
728 
729 				if (is(o, 'array') && is(o[0], 'string'))
730 					o = o.join(',#');
731 				o = $(is(o, 'string') ? '#' + o : o);
732 				if (n == 'init') {
733 					o.ready(cb, s);
734 				} else {
735 					if (s) {
736 						o.bind(n, s, cb);
737 					} else {
738 						o.bind(n, cb);
739 					}
740 				}
741 
742 				lo = this._jqLookup || (this._jqLookup = []);
743 				lo.push({func : f, cfunc : cb});
744 
745 				return cb;
746 			},
747 
748 			remove : function(o, n, f) {
749 				// Find cfunc
750 				$(this._jqLookup).each(function() {
751 					if (this.func === f)
752 						f = this.cfunc;
753 				});
754 
755 				if (is(o, 'array') && is(o[0], 'string'))
756 					o = o.join(',#');
757 
758 				$(is(o, 'string') ? '#' + o : o).unbind(n,f);
759 
760 				return true;
761 			}
762 		}
763 */
764 	};
765 
766 	// Patch functions after a class is created
767 	tinymce.onCreate = function(ty, c, p) {
768 		tinymce.extend(p, patches[c]);
769 	};
770 })(window.jQuery, tinymce);
771 
772 
773 
774 tinymce.create('tinymce.util.Dispatcher', {
775 	scope : null,
776 	listeners : null,
777 	inDispatch: false,
778 
779 	Dispatcher : function(scope) {
780 		this.scope = scope || this;
781 		this.listeners = [];
782 	},
783 
784 	add : function(callback, scope) {
785 		this.listeners.push({cb : callback, scope : scope || this.scope});
786 
787 		return callback;
788 	},
789 
790 	addToTop : function(callback, scope) {
791 		var self = this, listener = {cb : callback, scope : scope || self.scope};
792 
793 		// Create new listeners if addToTop is executed in a dispatch loop
794 		if (self.inDispatch) {
795 			self.listeners = [listener].concat(self.listeners);
796 		} else {
797 			self.listeners.unshift(listener);
798 		}
799 
800 		return callback;
801 	},
802 
803 	remove : function(callback) {
804 		var listeners = this.listeners, output = null;
805 
806 		tinymce.each(listeners, function(listener, i) {
807 			if (callback == listener.cb) {
808 				output = listener;
809 				listeners.splice(i, 1);
810 				return false;
811 			}
812 		});
813 
814 		return output;
815 	},
816 
817 	dispatch : function() {
818 		var self = this, returnValue, args = arguments, i, listeners = self.listeners, listener;
819 
820 		self.inDispatch = true;
821 		
822 		// Needs to be a real loop since the listener count might change while looping
823 		// And this is also more efficient
824 		for (i = 0; i < listeners.length; i++) {
825 			listener = listeners[i];
826 			returnValue = listener.cb.apply(listener.scope, args.length > 0 ? args : [listener.scope]);
827 
828 			if (returnValue === false)
829 				break;
830 		}
831 
832 		self.inDispatch = false;
833 
834 		return returnValue;
835 	}
836 
837 	});
838 
839 (function() {
840 	var each = tinymce.each;
841 
842 	tinymce.create('tinymce.util.URI', {
843 		URI : function(u, s) {
844 			var t = this, o, a, b, base_url;
845 
846 			// Trim whitespace
847 			u = tinymce.trim(u);
848 
849 			// Default settings
850 			s = t.settings = s || {};
851 
852 			// Strange app protocol that isn't http/https or local anchor
853 			// For example: mailto,skype,tel etc.
854 			if (/^([\w\-]+):([^\/]{2})/i.test(u) || /^\s*#/.test(u)) {
855 				t.source = u;
856 				return;
857 			}
858 
859 			// Absolute path with no host, fake host and protocol
860 			if (u.indexOf('/') === 0 && u.indexOf('//') !== 0)
861 				u = (s.base_uri ? s.base_uri.protocol || 'http' : 'http') + '://mce_host' + u;
862 
863 			// Relative path http:// or protocol relative //path
864 			if (!/^[\w\-]*:?\/\//.test(u)) {
865 				base_url = s.base_uri ? s.base_uri.path : new tinymce.util.URI(location.href).directory;
866 				u = ((s.base_uri && s.base_uri.protocol) || 'http') + '://mce_host' + t.toAbsPath(base_url, u);
867 			}
868 
869 			// Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri)
870 			u = u.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something
871 			u = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(u);
872 			each(["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], function(v, i) {
873 				var s = u[i];
874 
875 				// Zope 3 workaround, they use @@something
876 				if (s)
877 					s = s.replace(/\(mce_at\)/g, '@@');
878 
879 				t[v] = s;
880 			});
881 
882 			b = s.base_uri;
883 			if (b) {
884 				if (!t.protocol)
885 					t.protocol = b.protocol;
886 
887 				if (!t.userInfo)
888 					t.userInfo = b.userInfo;
889 
890 				if (!t.port && t.host === 'mce_host')
891 					t.port = b.port;
892 
893 				if (!t.host || t.host === 'mce_host')
894 					t.host = b.host;
895 
896 				t.source = '';
897 			}
898 
899 			//t.path = t.path || '/';
900 		},
901 
902 		setPath : function(p) {
903 			var t = this;
904 
905 			p = /^(.*?)\/?(\w+)?$/.exec(p);
906 
907 			// Update path parts
908 			t.path = p[0];
909 			t.directory = p[1];
910 			t.file = p[2];
911 
912 			// Rebuild source
913 			t.source = '';
914 			t.getURI();
915 		},
916 
917 		toRelative : function(u) {
918 			var t = this, o;
919 
920 			if (u === "./")
921 				return u;
922 
923 			u = new tinymce.util.URI(u, {base_uri : t});
924 
925 			// Not on same domain/port or protocol
926 			if ((u.host != 'mce_host' && t.host != u.host && u.host) || t.port != u.port || t.protocol != u.protocol)
927 				return u.getURI();
928 
929 			var tu = t.getURI(), uu = u.getURI();
930 			
931 			// Allow usage of the base_uri when relative_urls = true
932 			if(tu == uu || (tu.charAt(tu.length - 1) == "/" && tu.substr(0, tu.length - 1) == uu))
933 				return tu;
934 
935 			o = t.toRelPath(t.path, u.path);
936 
937 			// Add query
938 			if (u.query)
939 				o += '?' + u.query;
940 
941 			// Add anchor
942 			if (u.anchor)
943 				o += '#' + u.anchor;
944 
945 			return o;
946 		},
947 	
948 		toAbsolute : function(u, nh) {
949 			u = new tinymce.util.URI(u, {base_uri : this});
950 
951 			return u.getURI(this.host == u.host && this.protocol == u.protocol ? nh : 0);
952 		},
953 
954 		toRelPath : function(base, path) {
955 			var items, bp = 0, out = '', i, l;
956 
957 			// Split the paths
958 			base = base.substring(0, base.lastIndexOf('/'));
959 			base = base.split('/');
960 			items = path.split('/');
961 
962 			if (base.length >= items.length) {
963 				for (i = 0, l = base.length; i < l; i++) {
964 					if (i >= items.length || base[i] != items[i]) {
965 						bp = i + 1;
966 						break;
967 					}
968 				}
969 			}
970 
971 			if (base.length < items.length) {
972 				for (i = 0, l = items.length; i < l; i++) {
973 					if (i >= base.length || base[i] != items[i]) {
974 						bp = i + 1;
975 						break;
976 					}
977 				}
978 			}
979 
980 			if (bp === 1)
981 				return path;
982 
983 			for (i = 0, l = base.length - (bp - 1); i < l; i++)
984 				out += "../";
985 
986 			for (i = bp - 1, l = items.length; i < l; i++) {
987 				if (i != bp - 1)
988 					out += "/" + items[i];
989 				else
990 					out += items[i];
991 			}
992 
993 			return out;
994 		},
995 
996 		toAbsPath : function(base, path) {
997 			var i, nb = 0, o = [], tr, outPath;
998 
999 			// Split paths
1000 			tr = /\/$/.test(path) ? '/' : '';
1001 			base = base.split('/');
1002 			path = path.split('/');
1003 
1004 			// Remove empty chunks
1005 			each(base, function(k) {
1006 				if (k)
1007 					o.push(k);
1008 			});
1009 
1010 			base = o;
1011 
1012 			// Merge relURLParts chunks
1013 			for (i = path.length - 1, o = []; i >= 0; i--) {
1014 				// Ignore empty or .
1015 				if (path[i].length === 0 || path[i] === ".")
1016 					continue;
1017 
1018 				// Is parent
1019 				if (path[i] === '..') {
1020 					nb++;
1021 					continue;
1022 				}
1023 
1024 				// Move up
1025 				if (nb > 0) {
1026 					nb--;
1027 					continue;
1028 				}
1029 
1030 				o.push(path[i]);
1031 			}
1032 
1033 			i = base.length - nb;
1034 
1035 			// If /a/b/c or /
1036 			if (i <= 0)
1037 				outPath = o.reverse().join('/');
1038 			else
1039 				outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/');
1040 
1041 			// Add front / if it's needed
1042 			if (outPath.indexOf('/') !== 0)
1043 				outPath = '/' + outPath;
1044 
1045 			// Add traling / if it's needed
1046 			if (tr && outPath.lastIndexOf('/') !== outPath.length - 1)
1047 				outPath += tr;
1048 
1049 			return outPath;
1050 		},
1051 
1052 		getURI : function(nh) {
1053 			var s, t = this;
1054 
1055 			// Rebuild source
1056 			if (!t.source || nh) {
1057 				s = '';
1058 
1059 				if (!nh) {
1060 					if (t.protocol)
1061 						s += t.protocol + '://';
1062 
1063 					if (t.userInfo)
1064 						s += t.userInfo + '@';
1065 
1066 					if (t.host)
1067 						s += t.host;
1068 
1069 					if (t.port)
1070 						s += ':' + t.port;
1071 				}
1072 
1073 				if (t.path)
1074 					s += t.path;
1075 
1076 				if (t.query)
1077 					s += '?' + t.query;
1078 
1079 				if (t.anchor)
1080 					s += '#' + t.anchor;
1081 
1082 				t.source = s;
1083 			}
1084 
1085 			return t.source;
1086 		}
1087 	});
1088 })();
1089 
1090 (function() {
1091 	var each = tinymce.each;
1092 
1093 	tinymce.create('static tinymce.util.Cookie', {
1094 		getHash : function(n) {
1095 			var v = this.get(n), h;
1096 
1097 			if (v) {
1098 				each(v.split('&'), function(v) {
1099 					v = v.split('=');
1100 					h = h || {};
1101 					h[unescape(v[0])] = unescape(v[1]);
1102 				});
1103 			}
1104 
1105 			return h;
1106 		},
1107 
1108 		setHash : function(n, v, e, p, d, s) {
1109 			var o = '';
1110 
1111 			each(v, function(v, k) {
1112 				o += (!o ? '' : '&') + escape(k) + '=' + escape(v);
1113 			});
1114 
1115 			this.set(n, o, e, p, d, s);
1116 		},
1117 
1118 		get : function(n) {
1119 			var c = document.cookie, e, p = n + "=", b;
1120 
1121 			// Strict mode
1122 			if (!c)
1123 				return;
1124 
1125 			b = c.indexOf("; " + p);
1126 
1127 			if (b == -1) {
1128 				b = c.indexOf(p);
1129 
1130 				if (b !== 0)
1131 					return null;
1132 			} else
1133 				b += 2;
1134 
1135 			e = c.indexOf(";", b);
1136 
1137 			if (e == -1)
1138 				e = c.length;
1139 
1140 			return unescape(c.substring(b + p.length, e));
1141 		},
1142 
1143 		set : function(n, v, e, p, d, s) {
1144 			document.cookie = n + "=" + escape(v) +
1145 				((e) ? "; expires=" + e.toGMTString() : "") +
1146 				((p) ? "; path=" + escape(p) : "") +
1147 				((d) ? "; domain=" + d : "") +
1148 				((s) ? "; secure" : "");
1149 		},
1150 
1151 		remove : function(name, path, domain) {
1152 			var date = new Date();
1153 
1154 			date.setTime(date.getTime() - 1000);
1155 
1156 			this.set(name, '', date, path, domain);
1157 		}
1158 	});
1159 })();
1160 
1161 (function() {
1162 	function serialize(o, quote) {
1163 		var i, v, t, name;
1164 
1165 		quote = quote || '"';
1166 
1167 		if (o == null)
1168 			return 'null';
1169 
1170 		t = typeof o;
1171 
1172 		if (t == 'string') {
1173 			v = '\bb\tt\nn\ff\rr\""\'\'\\\\';
1174 
1175 			return quote + o.replace(/([\u0080-\uFFFF\x00-\x1f\"\'\\])/g, function(a, b) {
1176 				// Make sure single quotes never get encoded inside double quotes for JSON compatibility
1177 				if (quote === '"' && a === "'")
1178 					return a;
1179 
1180 				i = v.indexOf(b);
1181 
1182 				if (i + 1)
1183 					return '\\' + v.charAt(i + 1);
1184 
1185 				a = b.charCodeAt().toString(16);
1186 
1187 				return '\\u' + '0000'.substring(a.length) + a;
1188 			}) + quote;
1189 		}
1190 
1191 		if (t == 'object') {
1192 			if (o.hasOwnProperty && o instanceof Array) {
1193 					for (i=0, v = '['; i<o.length; i++)
1194 						v += (i > 0 ? ',' : '') + serialize(o[i], quote);
1195 
1196 					return v + ']';
1197 				}
1198 
1199 				v = '{';
1200 
1201 				for (name in o) {
1202 					if (o.hasOwnProperty(name)) {
1203 						v += typeof o[name] != 'function' ? (v.length > 1 ? ',' + quote : quote) + name + quote +':' + serialize(o[name], quote) : '';
1204 					}
1205 				}
1206 
1207 				return v + '}';
1208 		}
1209 
1210 		return '' + o;
1211 	};
1212 
1213 	tinymce.util.JSON = {
1214 		serialize: serialize,
1215 
1216 		parse: function(s) {
1217 			try {
1218 				return eval('(' + s + ')');
1219 			} catch (ex) {
1220 				// Ignore
1221 			}
1222 		}
1223 
1224 		};
1225 })();
1226 
1227 tinymce.create('static tinymce.util.XHR', {
1228 	send : function(o) {
1229 		var x, t, w = window, c = 0;
1230 
1231 		function ready() {
1232 			if (!o.async || x.readyState == 4 || c++ > 10000) {
1233 				if (o.success && c < 10000 && x.status == 200)
1234 					o.success.call(o.success_scope, '' + x.responseText, x, o);
1235 				else if (o.error)
1236 					o.error.call(o.error_scope, c > 10000 ? 'TIMED_OUT' : 'GENERAL', x, o);
1237 
1238 				x = null;
1239 			} else
1240 				w.setTimeout(ready, 10);
1241 		};
1242 
1243 		// Default settings
1244 		o.scope = o.scope || this;
1245 		o.success_scope = o.success_scope || o.scope;
1246 		o.error_scope = o.error_scope || o.scope;
1247 		o.async = o.async === false ? false : true;
1248 		o.data = o.data || '';
1249 
1250 		function get(s) {
1251 			x = 0;
1252 
1253 			try {
1254 				x = new ActiveXObject(s);
1255 			} catch (ex) {
1256 			}
1257 
1258 			return x;
1259 		};
1260 
1261 		x = w.XMLHttpRequest ? new XMLHttpRequest() : get('Microsoft.XMLHTTP') || get('Msxml2.XMLHTTP');
1262 
1263 		if (x) {
1264 			if (x.overrideMimeType)
1265 				x.overrideMimeType(o.content_type);
1266 
1267 			x.open(o.type || (o.data ? 'POST' : 'GET'), o.url, o.async);
1268 
1269 			if (o.content_type)
1270 				x.setRequestHeader('Content-Type', o.content_type);
1271 
1272 			x.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
1273 
1274 			x.send(o.data);
1275 
1276 			// Syncronous request
1277 			if (!o.async)
1278 				return ready();
1279 
1280 			// Wait for response, onReadyStateChange can not be used since it leaks memory in IE
1281 			t = w.setTimeout(ready, 10);
1282 		}
1283 	}
1284 });
1285 
1286 (function() {
1287 	var extend = tinymce.extend, JSON = tinymce.util.JSON, XHR = tinymce.util.XHR;
1288 
1289 	tinymce.create('tinymce.util.JSONRequest', {
1290 		JSONRequest : function(s) {
1291 			this.settings = extend({
1292 			}, s);
1293 			this.count = 0;
1294 		},
1295 
1296 		send : function(o) {
1297 			var ecb = o.error, scb = o.success;
1298 
1299 			o = extend(this.settings, o);
1300 
1301 			o.success = function(c, x) {
1302 				c = JSON.parse(c);
1303 
1304 				if (typeof(c) == 'undefined') {
1305 					c = {
1306 						error : 'JSON Parse error.'
1307 					};
1308 				}
1309 
1310 				if (c.error)
1311 					ecb.call(o.error_scope || o.scope, c.error, x);
1312 				else
1313 					scb.call(o.success_scope || o.scope, c.result);
1314 			};
1315 
1316 			o.error = function(ty, x) {
1317 				if (ecb)
1318 					ecb.call(o.error_scope || o.scope, ty, x);
1319 			};
1320 
1321 			o.data = JSON.serialize({
1322 				id : o.id || 'c' + (this.count++),
1323 				method : o.method,
1324 				params : o.params
1325 			});
1326 
1327 			// JSON content type for Ruby on rails. Bug: #1883287
1328 			o.content_type = 'application/json';
1329 
1330 			XHR.send(o);
1331 		},
1332 
1333 		'static' : {
1334 			sendRPC : function(o) {
1335 				return new tinymce.util.JSONRequest().send(o);
1336 			}
1337 		}
1338 	});
1339 }());
1340 (function(tinymce){
1341 	tinymce.VK = {
1342 		BACKSPACE: 8,
1343 		DELETE: 46,
1344 		DOWN: 40,
1345 		ENTER: 13,
1346 		LEFT: 37,
1347 		RIGHT: 39,
1348 		SPACEBAR: 32,
1349 		TAB: 9,
1350 		UP: 38,
1351 
1352 		modifierPressed: function (e) {
1353 			return e.shiftKey || e.ctrlKey || e.altKey;
1354 		},
1355 
1356 		metaKeyPressed: function(e) {
1357 			return tinymce.isMac ? e.metaKey : e.ctrlKey;
1358 		}
1359 	};
1360 })(tinymce);
1361 
1362 tinymce.util.Quirks = function(editor) {
1363 	var VK = tinymce.VK, BACKSPACE = VK.BACKSPACE, DELETE = VK.DELETE, dom = editor.dom, selection = editor.selection, settings = editor.settings;
1364 
1365 	function setEditorCommandState(cmd, state) {
1366 		try {
1367 			editor.getDoc().execCommand(cmd, false, state);
1368 		} catch (ex) {
1369 			// Ignore
1370 		}
1371 	}
1372 
1373 	function getDocumentMode() {
1374 		var documentMode = editor.getDoc().documentMode;
1375 
1376 		return documentMode ? documentMode : 6;
1377 	};
1378 
1379 	function cleanupStylesWhenDeleting() {
1380 		function removeMergedFormatSpans(isDelete) {
1381 			var rng, blockElm, node, clonedSpan;
1382 
1383 			rng = selection.getRng();
1384 
1385 			// Find root block
1386 			blockElm = dom.getParent(rng.startContainer, dom.isBlock);
1387 
1388 			// On delete clone the root span of the next block element
1389 			if (isDelete)
1390 				blockElm = dom.getNext(blockElm, dom.isBlock);
1391 
1392 			// Locate root span element and clone it since it would otherwise get merged by the "apple-style-span" on delete/backspace
1393 			if (blockElm) {
1394 				node = blockElm.firstChild;
1395 
1396 				// Ignore empty text nodes
1397 				while (node && node.nodeType == 3 && node.nodeValue.length === 0)
1398 					node = node.nextSibling;
1399 
1400 				if (node && node.nodeName === 'SPAN') {
1401 					clonedSpan = node.cloneNode(false);
1402 				}
1403 			}
1404 
1405 			// Do the backspace/delete action
1406 			editor.getDoc().execCommand(isDelete ? 'ForwardDelete' : 'Delete', false, null);
1407 
1408 			// Find all odd apple-style-spans
1409 			blockElm = dom.getParent(rng.startContainer, dom.isBlock);
1410 			tinymce.each(dom.select('span.Apple-style-span,font.Apple-style-span', blockElm), function(span) {
1411 				var bm = selection.getBookmark();
1412 
1413 				if (clonedSpan) {
1414 					dom.replace(clonedSpan.cloneNode(false), span, true);
1415 				} else {
1416 					dom.remove(span, true);
1417 				}
1418 
1419 				// Restore the selection
1420 				selection.moveToBookmark(bm);
1421 			});
1422 		};
1423 
1424 		editor.onKeyDown.add(function(editor, e) {
1425 			var isDelete;
1426 
1427 			isDelete = e.keyCode == DELETE;
1428 			if (!e.isDefaultPrevented() && (isDelete || e.keyCode == BACKSPACE) && !VK.modifierPressed(e)) {
1429 				e.preventDefault();
1430 				removeMergedFormatSpans(isDelete);
1431 			}
1432 		});
1433 
1434 		editor.addCommand('Delete', function() {removeMergedFormatSpans();});
1435 	};
1436 	
1437 	function emptyEditorWhenDeleting() {
1438 		function serializeRng(rng) {
1439 			var body = dom.create("body");
1440 			var contents = rng.cloneContents();
1441 			body.appendChild(contents);
1442 			return selection.serializer.serialize(body, {format: 'html'});
1443 		}
1444 
1445 		function allContentsSelected(rng) {
1446 			var selection = serializeRng(rng);
1447 
1448 			var allRng = dom.createRng();
1449 			allRng.selectNode(editor.getBody());
1450 
1451 			var allSelection = serializeRng(allRng);//console.log(selection, "----", allSelection);
1452 			return selection === allSelection;
1453 		}
1454 
1455 		editor.onKeyDown.add(function(editor, e) {
1456 			var keyCode = e.keyCode, isCollapsed;
1457 
1458 			// Empty the editor if it's needed for example backspace at <p><b>|</b></p>
1459 			if (!e.isDefaultPrevented() && (keyCode == DELETE || keyCode == BACKSPACE)) {
1460 				isCollapsed = editor.selection.isCollapsed();
1461 
1462 				// Selection is collapsed but the editor isn't empty
1463 				if (isCollapsed && !dom.isEmpty(editor.getBody())) {
1464 					return;
1465 				}
1466 
1467 				// IE deletes all contents correctly when everything is selected
1468 				if (tinymce.isIE && !isCollapsed) {
1469 					return;
1470 				}
1471 
1472 				// Selection isn't collapsed but not all the contents is selected
1473 				if (!isCollapsed && !allContentsSelected(editor.selection.getRng())) {
1474 					return;
1475 				}
1476 
1477 				// Manually empty the editor
1478 				editor.setContent('');
1479 				editor.selection.setCursorLocation(editor.getBody(), 0);
1480 				editor.nodeChanged();
1481 			}
1482 		});
1483 	};
1484 
1485 	function selectAll() {
1486 		editor.onKeyDown.add(function(editor, e) {
1487 			if (e.keyCode == 65 && VK.metaKeyPressed(e)) {
1488 				e.preventDefault();
1489 				editor.execCommand('SelectAll');
1490 			}
1491 		});
1492 	};
1493 
1494 	function inputMethodFocus() {
1495 		if (!editor.settings.content_editable) {
1496 			// Case 1 IME doesn't initialize if you focus the document
1497 			dom.bind(editor.getDoc(), 'focusin', function(e) {
1498 				selection.setRng(selection.getRng());
1499 			});
1500 
1501 			// Case 2 IME doesn't initialize if you click the documentElement it also doesn't properly fire the focusin event
1502 			dom.bind(editor.getDoc(), 'mousedown', function(e) {
1503 				if (e.target == editor.getDoc().documentElement) {
1504 					editor.getWin().focus();
1505 					selection.setRng(selection.getRng());
1506 				}
1507 			});
1508 		}
1509 	};
1510 
1511 	function removeHrOnBackspace() {
1512 		editor.onKeyDown.add(function(editor, e) {
1513 			if (!e.isDefaultPrevented() && e.keyCode === BACKSPACE) {
1514 				if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) {
1515 					var node = selection.getNode();
1516 					var previousSibling = node.previousSibling;
1517 
1518 					if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "hr") {
1519 						dom.remove(previousSibling);
1520 						tinymce.dom.Event.cancel(e);
1521 					}
1522 				}
1523 			}
1524 		})
1525 	}
1526 
1527 	function focusBody() {
1528 		// Fix for a focus bug in FF 3.x where the body element
1529 		// wouldn't get proper focus if the user clicked on the HTML element
1530 		if (!Range.prototype.getClientRects) { // Detect getClientRects got introduced in FF 4
1531 			editor.onMouseDown.add(function(editor, e) {
1532 				if (e.target.nodeName === "HTML") {
1533 					var body = editor.getBody();
1534 
1535 					// Blur the body it's focused but not correctly focused
1536 					body.blur();
1537 
1538 					// Refocus the body after a little while
1539 					setTimeout(function() {
1540 						body.focus();
1541 					}, 0);
1542 				}
1543 			});
1544 		}
1545 	};
1546 
1547 	function selectControlElements() {
1548 		editor.onClick.add(function(editor, e) {
1549 			e = e.target;
1550 
1551 			// Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250
1552 			// WebKit can't even do simple things like selecting an image
1553 			// Needs tobe the setBaseAndExtend or it will fail to select floated images
1554 			if (/^(IMG|HR)$/.test(e.nodeName)) {
1555 				selection.getSel().setBaseAndExtent(e, 0, e, 1);
1556 			}
1557 
1558 			if (e.nodeName == 'A' && dom.hasClass(e, 'mceItemAnchor')) {
1559 				selection.select(e);
1560 			}
1561 
1562 			editor.nodeChanged();
1563 		});
1564 	};
1565 
1566 	function removeStylesWhenDeletingAccrossBlockElements() {
1567 		function getAttributeApplyFunction() {
1568 			var template = dom.getAttribs(selection.getStart().cloneNode(false));
1569 
1570 			return function() {
1571 				var target = selection.getStart();
1572 
1573 				if (target !== editor.getBody()) {
1574 					dom.setAttrib(target, "style", null);
1575 
1576 					tinymce.each(template, function(attr) {
1577 						target.setAttributeNode(attr.cloneNode(true));
1578 					});
1579 				}
1580 			};
1581 		}
1582 
1583 		function isSelectionAcrossElements() {
1584 			return !selection.isCollapsed() && selection.getStart() != selection.getEnd();
1585 		}
1586 
1587 		function blockEvent(editor, e) {
1588 			e.preventDefault();
1589 			return false;
1590 		}
1591 
1592 		editor.onKeyPress.add(function(editor, e) {
1593 			var applyAttributes;
1594 
1595 			if ((e.keyCode == 8 || e.keyCode == 46) && isSelectionAcrossElements()) {
1596 				applyAttributes = getAttributeApplyFunction();
1597 				editor.getDoc().execCommand('delete', false, null);
1598 				applyAttributes();
1599 				e.preventDefault();
1600 				return false;
1601 			}
1602 		});
1603 
1604 		dom.bind(editor.getDoc(), 'cut', function(e) {
1605 			var applyAttributes;
1606 
1607 			if (isSelectionAcrossElements()) {
1608 				applyAttributes = getAttributeApplyFunction();
1609 				editor.onKeyUp.addToTop(blockEvent);
1610 
1611 				setTimeout(function() {
1612 					applyAttributes();
1613 					editor.onKeyUp.remove(blockEvent);
1614 				}, 0);
1615 			}
1616 		});
1617 	}
1618 
1619 	function selectionChangeNodeChanged() {
1620 		var lastRng, selectionTimer;
1621 
1622 		dom.bind(editor.getDoc(), 'selectionchange', function() {
1623 			if (selectionTimer) {
1624 				clearTimeout(selectionTimer);
1625 				selectionTimer = 0;
1626 			}
1627 
1628 			selectionTimer = window.setTimeout(function() {
1629 				var rng = selection.getRng();
1630 
1631 				// Compare the ranges to see if it was a real change or not
1632 				if (!lastRng || !tinymce.dom.RangeUtils.compareRanges(rng, lastRng)) {
1633 					editor.nodeChanged();
1634 					lastRng = rng;
1635 				}
1636 			}, 50);
1637 		});
1638 	}
1639 
1640 	function ensureBodyHasRoleApplication() {
1641 		document.body.setAttribute("role", "application");
1642 	}
1643 
1644 	function disableBackspaceIntoATable() {
1645 		editor.onKeyDown.add(function(editor, e) {
1646 			if (!e.isDefaultPrevented() && e.keyCode === BACKSPACE) {
1647 				if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) {
1648 					var previousSibling = selection.getNode().previousSibling;
1649 					if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "table") {
1650 						return tinymce.dom.Event.cancel(e);
1651 					}
1652 				}
1653 			}
1654 		})
1655 	}
1656 
1657 	function addNewLinesBeforeBrInPre() {
1658 		// IE8+ rendering mode does the right thing with BR in PRE
1659 		if (getDocumentMode() > 7) {
1660 			return;
1661 		}
1662 
1663 		 // Enable display: none in area and add a specific class that hides all BR elements in PRE to
1664 		 // avoid the caret from getting stuck at the BR elements while pressing the right arrow key
1665 		setEditorCommandState('RespectVisibilityInDesign', true);
1666 		editor.contentStyles.push('.mceHideBrInPre pre br {display: none}');
1667 		dom.addClass(editor.getBody(), 'mceHideBrInPre');
1668 
1669 		// Adds a \n before all BR elements in PRE to get them visual
1670 		editor.parser.addNodeFilter('pre', function(nodes, name) {
1671 			var i = nodes.length, brNodes, j, brElm, sibling;
1672 
1673 			while (i--) {
1674 				brNodes = nodes[i].getAll('br');
1675 				j = brNodes.length;
1676 				while (j--) {
1677 					brElm = brNodes[j];
1678 
1679 					// Add \n before BR in PRE elements on older IE:s so the new lines get rendered
1680 					sibling = brElm.prev;
1681 					if (sibling && sibling.type === 3 && sibling.value.charAt(sibling.value - 1) != '\n') {
1682 						sibling.value += '\n';
1683 					} else {
1684 						brElm.parent.insert(new tinymce.html.Node('#text', 3), brElm, true).value = '\n';
1685 					}
1686 				}
1687 			}
1688 		});
1689 
1690 		// Removes any \n before BR elements in PRE since other browsers and in contentEditable=false mode they will be visible
1691 		editor.serializer.addNodeFilter('pre', function(nodes, name) {
1692 			var i = nodes.length, brNodes, j, brElm, sibling;
1693 
1694 			while (i--) {
1695 				brNodes = nodes[i].getAll('br');
1696 				j = brNodes.length;
1697 				while (j--) {
1698 					brElm = brNodes[j];
1699 					sibling = brElm.prev;
1700 					if (sibling && sibling.type == 3) {
1701 						sibling.value = sibling.value.replace(/\r?\n$/, '');
1702 					}
1703 				}
1704 			}
1705 		});
1706 	}
1707 
1708 	function removePreSerializedStylesWhenSelectingControls() {
1709 		dom.bind(editor.getBody(), 'mouseup', function(e) {
1710 			var value, node = selection.getNode();
1711 
1712 			// Moved styles to attributes on IMG eements
1713 			if (node.nodeName == 'IMG') {
1714 				// Convert style width to width attribute
1715 				if (value = dom.getStyle(node, 'width')) {
1716 					dom.setAttrib(node, 'width', value.replace(/[^0-9%]+/g, ''));
1717 					dom.setStyle(node, 'width', '');
1718 				}
1719 
1720 				// Convert style height to height attribute
1721 				if (value = dom.getStyle(node, 'height')) {
1722 					dom.setAttrib(node, 'height', value.replace(/[^0-9%]+/g, ''));
1723 					dom.setStyle(node, 'height', '');
1724 				}
1725 			}
1726 		});
1727 	}
1728 
1729 	function keepInlineElementOnDeleteBackspace() {
1730 		editor.onKeyDown.add(function(editor, e) {
1731 			var isDelete, rng, container, offset, brElm, sibling, collapsed;
1732 
1733 			isDelete = e.keyCode == DELETE;
1734 			if (!e.isDefaultPrevented() && (isDelete || e.keyCode == BACKSPACE) && !VK.modifierPressed(e)) {
1735 				rng = selection.getRng();
1736 				container = rng.startContainer;
1737 				offset = rng.startOffset;
1738 				collapsed = rng.collapsed;
1739 
1740 				// Override delete if the start container is a text node and is at the beginning of text or
1741 				// just before/after the last character to be deleted in collapsed mode
1742 				if (container.nodeType == 3 && container.nodeValue.length > 0 && ((offset === 0 && !collapsed) || (collapsed && offset === (isDelete ? 0 : 1)))) {
1743 					nonEmptyElements = editor.schema.getNonEmptyElements();
1744 
1745 					// Prevent default logic since it's broken
1746 					e.preventDefault();
1747 
1748 					// Insert a BR before the text node this will prevent the containing element from being deleted/converted
1749 					brElm = dom.create('br', {id: '__tmp'});
1750 					container.parentNode.insertBefore(brElm, container);
1751 
1752 					// Do the browser delete
1753 					editor.getDoc().execCommand(isDelete ? 'ForwardDelete' : 'Delete', false, null);
1754 
1755 					// Check if the previous sibling is empty after deleting for example: <p><b></b>|</p>
1756 					container = selection.getRng().startContainer;
1757 					sibling = container.previousSibling;
1758 					if (sibling && sibling.nodeType == 1 && !dom.isBlock(sibling) && dom.isEmpty(sibling) && !nonEmptyElements[sibling.nodeName.toLowerCase()]) {
1759 						dom.remove(sibling);
1760 					}
1761 
1762 					// Remove the temp element we inserted
1763 					dom.remove('__tmp');
1764 				}
1765 			}
1766 		});
1767 	}
1768 
1769 	function removeBlockQuoteOnBackSpace() {
1770 		// Add block quote deletion handler
1771 		editor.onKeyDown.add(function(editor, e) {
1772 			var rng, container, offset, root, parent;
1773 
1774 			if (e.isDefaultPrevented() || e.keyCode != VK.BACKSPACE) {
1775 				return;
1776 			}
1777 
1778 			rng = selection.getRng();
1779 			container = rng.startContainer;
1780 			offset = rng.startOffset;
1781 			root = dom.getRoot();
1782 			parent = container;
1783 
1784 			if (!rng.collapsed || offset !== 0) {
1785 				return;
1786 			}
1787 
1788 			while (parent && parent.parentNode && parent.parentNode.firstChild == parent && parent.parentNode != root) {
1789 				parent = parent.parentNode;
1790 			}
1791 
1792 			// Is the cursor at the beginning of a blockquote?
1793 			if (parent.tagName === 'BLOCKQUOTE') {
1794 				// Remove the blockquote
1795 				editor.formatter.toggle('blockquote', null, parent);
1796 
1797 				// Move the caret to the beginning of container
1798 				rng.setStart(container, 0);
1799 				rng.setEnd(container, 0);
1800 				selection.setRng(rng);
1801 				selection.collapse(false);
1802 			}
1803 		});
1804 	};
1805 
1806 	function setGeckoEditingOptions() {
1807 		function setOpts() {
1808 			editor._refreshContentEditable();
1809 
1810 			setEditorCommandState("StyleWithCSS", false);
1811 			setEditorCommandState("enableInlineTableEditing", false);
1812 
1813 			if (!settings.object_resizing) {
1814 				setEditorCommandState("enableObjectResizing", false);
1815 			}
1816 		};
1817 
1818 		if (!settings.readonly) {
1819 			editor.onBeforeExecCommand.add(setOpts);
1820 			editor.onMouseDown.add(setOpts);
1821 		}
1822 	};
1823 
1824 	function addBrAfterLastLinks() {
1825 		function fixLinks(editor, o) {
1826 			tinymce.each(dom.select('a'), function(node) {
1827 				var parentNode = node.parentNode, root = dom.getRoot();
1828 
1829 				if (parentNode.lastChild === node) {
1830 					while (parentNode && !dom.isBlock(parentNode)) {
1831 						if (parentNode.parentNode.lastChild !== parentNode || parentNode === root) {
1832 							return;
1833 						}
1834 
1835 						parentNode = parentNode.parentNode;
1836 					}
1837 
1838 					dom.add(parentNode, 'br', {'data-mce-bogus' : 1});
1839 				}
1840 			});
1841 		};
1842 
1843 		editor.onExecCommand.add(function(editor, cmd) {
1844 			if (cmd === 'CreateLink') {
1845 				fixLinks(editor);
1846 			}
1847 		});
1848 
1849 		editor.onSetContent.add(selection.onSetContent.add(fixLinks));
1850 	};
1851 
1852 	function setDefaultBlockType() {
1853 		if (settings.forced_root_block) {
1854 			editor.onInit.add(function() {
1855 				setEditorCommandState('DefaultParagraphSeparator', settings.forced_root_block);
1856 			});
1857 		}
1858 	}
1859 
1860 	function removeGhostSelection() {
1861 		function repaint(sender, args) {
1862 			if (!sender || !args.initial) {
1863 				editor.execCommand('mceRepaint');
1864 			}
1865 		};
1866 
1867 		editor.onUndo.add(repaint);
1868 		editor.onRedo.add(repaint);
1869 		editor.onSetContent.add(repaint);
1870 	};
1871 
1872 	function deleteControlItemOnBackSpace() {
1873 		editor.onKeyDown.add(function(editor, e) {
1874 			var rng;
1875 
1876 			if (!e.isDefaultPrevented() && e.keyCode == BACKSPACE) {
1877 				rng = editor.getDoc().selection.createRange();
1878 				if (rng && rng.item) {
1879 					e.preventDefault();
1880 					editor.undoManager.beforeChange();
1881 					dom.remove(rng.item(0));
1882 					editor.undoManager.add();
1883 				}
1884 			}
1885 		});
1886 	};
1887 
1888 	function renderEmptyBlocksFix() {
1889 		var emptyBlocksCSS;
1890 
1891 		// IE10+
1892 		if (getDocumentMode() >= 10) {
1893 			emptyBlocksCSS = '';
1894 			tinymce.each('p div h1 h2 h3 h4 h5 h6'.split(' '), function(name, i) {
1895 				emptyBlocksCSS += (i > 0 ? ',' : '') + name + ':empty';
1896 			});
1897 
1898 			editor.contentStyles.push(emptyBlocksCSS + '{padding-right: 1px !important}');
1899 		}
1900 	};
1901 
1902 	function fakeImageResize() {
1903 		var mouseDownImg, startX, startY, startW, startH;
1904 
1905 		if (!settings.object_resizing || settings.webkit_fake_resize === false) {
1906 			return;
1907 		}
1908 
1909 		editor.contentStyles.push('.mceResizeImages img {cursor: se-resize !important}');
1910 
1911 		function resizeImage(e) {
1912 			var deltaX, deltaY, ratio, width, height;
1913 
1914 			if (mouseDownImg) {
1915 				deltaX = e.screenX - startX;
1916 				deltaY = e.screenY - startY;
1917 				ratio = Math.max((startW + deltaX) / startW, (startH + deltaY) / startH);
1918 
1919 				// Only update styles if the user draged one pixel or more
1920 				if (Math.abs(deltaX) > 1 || Math.abs(deltaY) > 1) {
1921 					// Constrain proportions
1922 					width = Math.round(startW * ratio);
1923 					height = Math.round(startH * ratio);
1924 
1925 					// Resize by using style or attribute
1926 					if (mouseDownImg.style.width) {
1927 						dom.setStyle(mouseDownImg, 'width', width);
1928 					} else {
1929 						dom.setAttrib(mouseDownImg, 'width', width);
1930 					}
1931 
1932 					// Resize by using style or attribute
1933 					if (mouseDownImg.style.height) {
1934 						dom.setStyle(mouseDownImg, 'height', height);
1935 					} else {
1936 						dom.setAttrib(mouseDownImg, 'height', height);
1937 					}
1938 
1939 					if (!dom.hasClass(editor.getBody(), 'mceResizeImages')) {
1940 						dom.addClass(editor.getBody(), 'mceResizeImages');
1941 					}
1942 				}
1943 			}
1944 		};
1945 
1946 		editor.onMouseDown.add(function(editor, e) {
1947 			var target = e.target;
1948 
1949 			if (target.nodeName == "IMG") {
1950 				mouseDownImg = target;
1951 				startX = e.screenX;
1952 				startY = e.screenY;
1953 				startW = mouseDownImg.clientWidth;
1954 				startH = mouseDownImg.clientHeight;
1955 				dom.bind(editor.getDoc(), 'mousemove', resizeImage);
1956 				e.preventDefault();
1957 			}
1958 		});
1959 
1960 		// Unbind events on node change and restore resize cursor
1961 		editor.onNodeChange.add(function() {
1962 			if (mouseDownImg) {
1963 				mouseDownImg = null;
1964 				dom.unbind(editor.getDoc(), 'mousemove', resizeImage);
1965 			}
1966 
1967 			if (selection.getNode().nodeName == "IMG") {
1968 				dom.addClass(editor.getBody(), 'mceResizeImages');
1969 			} else {
1970 				dom.removeClass(editor.getBody(), 'mceResizeImages');
1971 			}
1972 		});
1973 	};
1974 
1975 	// All browsers
1976 	disableBackspaceIntoATable();
1977 	removeBlockQuoteOnBackSpace();
1978 	emptyEditorWhenDeleting();
1979 
1980 	// WebKit
1981 	if (tinymce.isWebKit) {
1982 		keepInlineElementOnDeleteBackspace();
1983 		cleanupStylesWhenDeleting();
1984 		inputMethodFocus();
1985 		selectControlElements();
1986 		setDefaultBlockType();
1987 
1988 		// iOS
1989 		if (tinymce.isIDevice) {
1990 			selectionChangeNodeChanged();
1991 		} else {
1992 			fakeImageResize();
1993 			selectAll();
1994 		}
1995 	}
1996 
1997 	// IE
1998 	if (tinymce.isIE) {
1999 		removeHrOnBackspace();
2000 		ensureBodyHasRoleApplication();
2001 		addNewLinesBeforeBrInPre();
2002 		removePreSerializedStylesWhenSelectingControls();
2003 		deleteControlItemOnBackSpace();
2004 		renderEmptyBlocksFix();
2005 	}
2006 
2007 	// Gecko
2008 	if (tinymce.isGecko) {
2009 		removeHrOnBackspace();
2010 		focusBody();
2011 		removeStylesWhenDeletingAccrossBlockElements();
2012 		setGeckoEditingOptions();
2013 		addBrAfterLastLinks();
2014 		removeGhostSelection();
2015 	}
2016 };
2017 (function(tinymce) {
2018 	var namedEntities, baseEntities, reverseEntities,
2019 		attrsCharsRegExp = /[&<>\"\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
2020 		textCharsRegExp = /[<>&\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
2021 		rawCharsRegExp = /[<>&\"\']/g,
2022 		entityRegExp = /&(#x|#)?([\w]+);/g,
2023 		asciiMap = {
2024 				128 : "\u20AC", 130 : "\u201A", 131 : "\u0192", 132 : "\u201E", 133 : "\u2026", 134 : "\u2020",
2025 				135 : "\u2021", 136 : "\u02C6", 137 : "\u2030", 138 : "\u0160", 139 : "\u2039", 140 : "\u0152",
2026 				142 : "\u017D", 145 : "\u2018", 146 : "\u2019", 147 : "\u201C", 148 : "\u201D", 149 : "\u2022",
2027 				150 : "\u2013", 151 : "\u2014", 152 : "\u02DC", 153 : "\u2122", 154 : "\u0161", 155 : "\u203A",
2028 				156 : "\u0153", 158 : "\u017E", 159 : "\u0178"
2029 		};
2030 
2031 	// Raw entities
2032 	baseEntities = {
2033 		'\"' : '"', // Needs to be escaped since the YUI compressor would otherwise break the code
2034 		"'" : ''',
2035 		'<' : '<',
2036 		'>' : '>',
2037 		'&' : '&'
2038 	};
2039 
2040 	// Reverse lookup table for raw entities
2041 	reverseEntities = {
2042 		'<' : '<',
2043 		'>' : '>',
2044 		'&' : '&',
2045 		'"' : '"',
2046 		''' : "'"
2047 	};
2048 
2049 	// Decodes text by using the browser
2050 	function nativeDecode(text) {
2051 		var elm;
2052 
2053 		elm = document.createElement("div");
2054 		elm.innerHTML = text;
2055 
2056 		return elm.textContent || elm.innerText || text;
2057 	};
2058 
2059 	// Build a two way lookup table for the entities
2060 	function buildEntitiesLookup(items, radix) {
2061 		var i, chr, entity, lookup = {};
2062 
2063 		if (items) {
2064 			items = items.split(',');
2065 			radix = radix || 10;
2066 
2067 			// Build entities lookup table
2068 			for (i = 0; i < items.length; i += 2) {
2069 				chr = String.fromCharCode(parseInt(items[i], radix));
2070 
2071 				// Only add non base entities
2072 				if (!baseEntities[chr]) {
2073 					entity = '&' + items[i + 1] + ';';
2074 					lookup[chr] = entity;
2075 					lookup[entity] = chr;
2076 				}
2077 			}
2078 
2079 			return lookup;
2080 		}
2081 	};
2082 
2083 	// Unpack entities lookup where the numbers are in radix 32 to reduce the size
2084 	namedEntities = buildEntitiesLookup(
2085 		'50,nbsp,51,iexcl,52,cent,53,pound,54,curren,55,yen,56,brvbar,57,sect,58,uml,59,copy,' +
2086 		'5a,ordf,5b,laquo,5c,not,5d,shy,5e,reg,5f,macr,5g,deg,5h,plusmn,5i,sup2,5j,sup3,5k,acute,' +
2087 		'5l,micro,5m,para,5n,middot,5o,cedil,5p,sup1,5q,ordm,5r,raquo,5s,frac14,5t,frac12,5u,frac34,' +
2088 		'5v,iquest,60,Agrave,61,Aacute,62,Acirc,63,Atilde,64,Auml,65,Aring,66,AElig,67,Ccedil,' +
2089 		'68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,Iuml,6g,ETH,6h,Ntilde,' +
2090 		'6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,Ugrave,6q,Uacute,' +
2091 		'6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,acirc,73,atilde,74,auml,' +
2092 		'75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,igrave,7d,iacute,7e,icirc,' +
2093 		'7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,ouml,7n,divide,7o,oslash,' +
2094 		'7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fnof,sh,Alpha,si,Beta,' +
2095 		'sj,Gamma,sk,Delta,sl,Epsilon,sm,Zeta,sn,Eta,so,Theta,sp,Iota,sq,Kappa,sr,Lambda,ss,Mu,' +
2096 		'st,Nu,su,Xi,sv,Omicron,t0,Pi,t1,Rho,t3,Sigma,t4,Tau,t5,Upsilon,t6,Phi,t7,Chi,t8,Psi,' +
2097 		't9,Omega,th,alpha,ti,beta,tj,gamma,tk,delta,tl,epsilon,tm,zeta,tn,eta,to,theta,tp,iota,' +
2098 		'tq,kappa,tr,lambda,ts,mu,tt,nu,tu,xi,tv,omicron,u0,pi,u1,rho,u2,sigmaf,u3,sigma,u4,tau,' +
2099 		'u5,upsilon,u6,phi,u7,chi,u8,psi,u9,omega,uh,thetasym,ui,upsih,um,piv,812,bull,816,hellip,' +
2100 		'81i,prime,81j,Prime,81u,oline,824,frasl,88o,weierp,88h,image,88s,real,892,trade,89l,alefsym,' +
2101 		'8cg,larr,8ch,uarr,8ci,rarr,8cj,darr,8ck,harr,8dl,crarr,8eg,lArr,8eh,uArr,8ei,rArr,8ej,dArr,' +
2102 		'8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g9,notin,8gb,ni,8gf,prod,' +
2103 		'8gh,sum,8gi,minus,8gn,lowast,8gq,radic,8gt,prop,8gu,infin,8h0,ang,8h7,and,8h8,or,8h9,cap,8ha,cup,' +
2104 		'8hb,int,8hk,there4,8hs,sim,8i5,cong,8i8,asymp,8j0,ne,8j1,equiv,8j4,le,8j5,ge,8k2,sub,8k3,sup,8k4,' +
2105 		'nsub,8k6,sube,8k7,supe,8kl,oplus,8kn,otimes,8l5,perp,8m5,sdot,8o8,lceil,8o9,rceil,8oa,lfloor,8ob,' +
2106 		'rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,diams,ai,OElig,aj,oelig,b0,' +
2107 		'Scaron,b1,scaron,bo,Yuml,m6,circ,ms,tilde,802,ensp,803,emsp,809,thinsp,80c,zwnj,80d,zwj,80e,lrm,' +
2108 		'80f,rlm,80j,ndash,80k,mdash,80o,lsquo,80p,rsquo,80q,sbquo,80s,ldquo,80t,rdquo,80u,bdquo,810,dagger,' +
2109 		'811,Dagger,81g,permil,81p,lsaquo,81q,rsaquo,85c,euro', 32);
2110 
2111 	tinymce.html = tinymce.html || {};
2112 
2113 	tinymce.html.Entities = {
2114 		encodeRaw : function(text, attr) {
2115 			return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
2116 				return baseEntities[chr] || chr;
2117 			});
2118 		},
2119 
2120 		encodeAllRaw : function(text) {
2121 			return ('' + text).replace(rawCharsRegExp, function(chr) {
2122 				return baseEntities[chr] || chr;
2123 			});
2124 		},
2125 
2126 		encodeNumeric : function(text, attr) {
2127 			return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
2128 				// Multi byte sequence convert it to a single entity
2129 				if (chr.length > 1)
2130 					return '&#' + (((chr.charCodeAt(0) - 0xD800) * 0x400) + (chr.charCodeAt(1) - 0xDC00) + 0x10000) + ';';
2131 
2132 				return baseEntities[chr] || '&#' + chr.charCodeAt(0) + ';';
2133 			});
2134 		},
2135 
2136 		encodeNamed : function(text, attr, entities) {
2137 			entities = entities || namedEntities;
2138 
2139 			return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
2140 				return baseEntities[chr] || entities[chr] || chr;
2141 			});
2142 		},
2143 
2144 		getEncodeFunc : function(name, entities) {
2145 			var Entities = tinymce.html.Entities;
2146 
2147 			entities = buildEntitiesLookup(entities) || namedEntities;
2148 
2149 			function encodeNamedAndNumeric(text, attr) {
2150 				return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
2151 					return baseEntities[chr] || entities[chr] || '&#' + chr.charCodeAt(0) + ';' || chr;
2152 				});
2153 			};
2154 
2155 			function encodeCustomNamed(text, attr) {
2156 				return Entities.encodeNamed(text, attr, entities);
2157 			};
2158 
2159 			// Replace + with , to be compatible with previous TinyMCE versions
2160 			name = tinymce.makeMap(name.replace(/\+/g, ','));
2161 
2162 			// Named and numeric encoder
2163 			if (name.named && name.numeric)
2164 				return encodeNamedAndNumeric;
2165 
2166 			// Named encoder
2167 			if (name.named) {
2168 				// Custom names
2169 				if (entities)
2170 					return encodeCustomNamed;
2171 
2172 				return Entities.encodeNamed;
2173 			}
2174 
2175 			// Numeric
2176 			if (name.numeric)
2177 				return Entities.encodeNumeric;
2178 
2179 			// Raw encoder
2180 			return Entities.encodeRaw;
2181 		},
2182 
2183 		decode : function(text) {
2184 			return text.replace(entityRegExp, function(all, numeric, value) {
2185 				if (numeric) {
2186 					value = parseInt(value, numeric.length === 2 ? 16 : 10);
2187 
2188 					// Support upper UTF
2189 					if (value > 0xFFFF) {
2190 						value -= 0x10000;
2191 
2192 						return String.fromCharCode(0xD800 + (value >> 10), 0xDC00 + (value & 0x3FF));
2193 					} else
2194 						return asciiMap[value] || String.fromCharCode(value);
2195 				}
2196 
2197 				return reverseEntities[all] || namedEntities[all] || nativeDecode(all);
2198 			});
2199 		}
2200 	};
2201 })(tinymce);
2202 
2203 tinymce.html.Styles = function(settings, schema) {
2204 	var rgbRegExp = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/gi,
2205 		urlOrStrRegExp = /(?:url(?:(?:\(\s*\"([^\"]+)\"\s*\))|(?:\(\s*\'([^\']+)\'\s*\))|(?:\(\s*([^)\s]+)\s*\))))|(?:\'([^\']+)\')|(?:\"([^\"]+)\")/gi,
2206 		styleRegExp = /\s*([^:]+):\s*([^;]+);?/g,
2207 		trimRightRegExp = /\s+$/,
2208 		urlColorRegExp = /rgb/,
2209 		undef, i, encodingLookup = {}, encodingItems;
2210 
2211 	settings = settings || {};
2212 
2213 	encodingItems = '\\" \\\' \\; \\: ; : \uFEFF'.split(' ');
2214 	for (i = 0; i < encodingItems.length; i++) {
2215 		encodingLookup[encodingItems[i]] = '\uFEFF' + i;
2216 		encodingLookup['\uFEFF' + i] = encodingItems[i];
2217 	}
2218 
2219 	function toHex(match, r, g, b) {
2220 		function hex(val) {
2221 			val = parseInt(val).toString(16);
2222 
2223 			return val.length > 1 ? val : '0' + val; // 0 -> 00
2224 		};
2225 
2226 		return '#' + hex(r) + hex(g) + hex(b);
2227 	};
2228 
2229 	return {
2230 		toHex : function(color) {
2231 			return color.replace(rgbRegExp, toHex);
2232 		},
2233 
2234 		parse : function(css) {
2235 			var styles = {}, matches, name, value, isEncoded, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope || this;
2236 
2237 			function compress(prefix, suffix) {
2238 				var top, right, bottom, left;
2239 
2240 				// Get values and check it it needs compressing
2241 				top = styles[prefix + '-top' + suffix];
2242 				if (!top)
2243 					return;
2244 
2245 				right = styles[prefix + '-right' + suffix];
2246 				if (top != right)
2247 					return;
2248 
2249 				bottom = styles[prefix + '-bottom' + suffix];
2250 				if (right != bottom)
2251 					return;
2252 
2253 				left = styles[prefix + '-left' + suffix];
2254 				if (bottom != left)
2255 					return;
2256 
2257 				// Compress
2258 				styles[prefix + suffix] = left;
2259 				delete styles[prefix + '-top' + suffix];
2260 				delete styles[prefix + '-right' + suffix];
2261 				delete styles[prefix + '-bottom' + suffix];
2262 				delete styles[prefix + '-left' + suffix];
2263 			};
2264 
2265 			function canCompress(key) {
2266 				var value = styles[key], i;
2267 
2268 				if (!value || value.indexOf(' ') < 0)
2269 					return;
2270 
2271 				value = value.split(' ');
2272 				i = value.length;
2273 				while (i--) {
2274 					if (value[i] !== value[0])
2275 						return false;
2276 				}
2277 
2278 				styles[key] = value[0];
2279 
2280 				return true;
2281 			};
2282 
2283 			function compress2(target, a, b, c) {
2284 				if (!canCompress(a))
2285 					return;
2286 
2287 				if (!canCompress(b))
2288 					return;
2289 
2290 				if (!canCompress(c))
2291 					return;
2292 
2293 				// Compress
2294 				styles[target] = styles[a] + ' ' + styles[b] + ' ' + styles[c];
2295 				delete styles[a];
2296 				delete styles[b];
2297 				delete styles[c];
2298 			};
2299 
2300 			// Encodes the specified string by replacing all \" \' ; : with _<num>
2301 			function encode(str) {
2302 				isEncoded = true;
2303 
2304 				return encodingLookup[str];
2305 			};
2306 
2307 			// Decodes the specified string by replacing all _<num> with it's original value \" \' etc
2308 			// It will also decode the \" \' if keep_slashes is set to fale or omitted
2309 			function decode(str, keep_slashes) {
2310 				if (isEncoded) {
2311 					str = str.replace(/\uFEFF[0-9]/g, function(str) {
2312 						return encodingLookup[str];
2313 					});
2314 				}
2315 
2316 				if (!keep_slashes)
2317 					str = str.replace(/\\([\'\";:])/g, "$1");
2318 
2319 				return str;
2320 			};
2321 
2322 			function processUrl(match, url, url2, url3, str, str2) {
2323 				str = str || str2;
2324 
2325 				if (str) {
2326 					str = decode(str);
2327 
2328 					// Force strings into single quote format
2329 					return "'" + str.replace(/\'/g, "\\'") + "'";
2330 				}
2331 
2332 				url = decode(url || url2 || url3);
2333 
2334 				// Convert the URL to relative/absolute depending on config
2335 				if (urlConverter)
2336 					url = urlConverter.call(urlConverterScope, url, 'style');
2337 
2338 				// Output new URL format
2339 				return "url('" + url.replace(/\'/g, "\\'") + "')";
2340 			};
2341 
2342 			if (css) {
2343 				// Encode \" \' % and ; and : inside strings so they don't interfere with the style parsing
2344 				css = css.replace(/\\[\"\';:\uFEFF]/g, encode).replace(/\"[^\"]+\"|\'[^\']+\'/g, function(str) {
2345 					return str.replace(/[;:]/g, encode);
2346 				});
2347 
2348 				// Parse styles
2349 				while (matches = styleRegExp.exec(css)) {
2350 					name = matches[1].replace(trimRightRegExp, '').toLowerCase();
2351 					value = matches[2].replace(trimRightRegExp, '');
2352 
2353 					if (name && value.length > 0) {
2354 						// Opera will produce 700 instead of bold in their style values
2355 						if (name === 'font-weight' && value === '700')
2356 							value = 'bold';
2357 						else if (name === 'color' || name === 'background-color') // Lowercase colors like RED
2358 							value = value.toLowerCase();		
2359 
2360 						// Convert RGB colors to HEX
2361 						value = value.replace(rgbRegExp, toHex);
2362 
2363 						// Convert URLs and force them into url('value') format
2364 						value = value.replace(urlOrStrRegExp, processUrl);
2365 						styles[name] = isEncoded ? decode(value, true) : value;
2366 					}
2367 
2368 					styleRegExp.lastIndex = matches.index + matches[0].length;
2369 				}
2370 
2371 				// Compress the styles to reduce it's size for example IE will expand styles
2372 				compress("border", "");
2373 				compress("border", "-width");
2374 				compress("border", "-color");
2375 				compress("border", "-style");
2376 				compress("padding", "");
2377 				compress("margin", "");
2378 				compress2('border', 'border-width', 'border-style', 'border-color');
2379 
2380 				// Remove pointless border, IE produces these
2381 				if (styles.border === 'medium none')
2382 					delete styles.border;
2383 			}
2384 
2385 			return styles;
2386 		},
2387 
2388 		serialize : function(styles, element_name) {
2389 			var css = '', name, value;
2390 
2391 			function serializeStyles(name) {
2392 				var styleList, i, l, value;
2393 
2394 				styleList = schema.styles[name];
2395 				if (styleList) {
2396 					for (i = 0, l = styleList.length; i < l; i++) {
2397 						name = styleList[i];
2398 						value = styles[name];
2399 
2400 						if (value !== undef && value.length > 0)
2401 							css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
2402 					}
2403 				}
2404 			};
2405 
2406 			// Serialize styles according to schema
2407 			if (element_name && schema && schema.styles) {
2408 				// Serialize global styles and element specific styles
2409 				serializeStyles('*');
2410 				serializeStyles(element_name);
2411 			} else {
2412 				// Output the styles in the order they are inside the object
2413 				for (name in styles) {
2414 					value = styles[name];
2415 
2416 					if (value !== undef && value.length > 0)
2417 						css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
2418 				}
2419 			}
2420 
2421 			return css;
2422 		}
2423 	};
2424 };
2425 
2426 (function(tinymce) {
2427 	var mapCache = {}, makeMap = tinymce.makeMap, each = tinymce.each;
2428 
2429 	function split(str, delim) {
2430 		return str.split(delim || ',');
2431 	};
2432 
2433 	function unpack(lookup, data) {
2434 		var key, elements = {};
2435 
2436 		function replace(value) {
2437 			return value.replace(/[A-Z]+/g, function(key) {
2438 				return replace(lookup[key]);
2439 			});
2440 		};
2441 
2442 		// Unpack lookup
2443 		for (key in lookup) {
2444 			if (lookup.hasOwnProperty(key))
2445 				lookup[key] = replace(lookup[key]);
2446 		}
2447 
2448 		// Unpack and parse data into object map
2449 		replace(data).replace(/#/g, '#text').replace(/(\w+)\[([^\]]+)\]\[([^\]]*)\]/g, function(str, name, attributes, children) {
2450 			attributes = split(attributes, '|');
2451 
2452 			elements[name] = {
2453 				attributes : makeMap(attributes),
2454 				attributesOrder : attributes,
2455 				children : makeMap(children, '|', {'#comment' : {}})
2456 			}
2457 		});
2458 
2459 		return elements;
2460 	};
2461 
2462 	function getHTML5() {
2463 		var html5 = mapCache.html5;
2464 
2465 		if (!html5) {
2466 			html5 = mapCache.html5 = unpack({
2467 					A : 'id|accesskey|class|dir|draggable|item|hidden|itemprop|role|spellcheck|style|subject|title|onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup',
2468 					B : '#|a|abbr|area|audio|b|bdo|br|button|canvas|cite|code|command|datalist|del|dfn|em|embed|i|iframe|img|input|ins|kbd|keygen|label|link|map|mark|meta|' +
2469 						'meter|noscript|object|output|progress|q|ruby|samp|script|select|small|span|strong|sub|sup|svg|textarea|time|var|video|wbr',
2470 					C : '#|a|abbr|area|address|article|aside|audio|b|bdo|blockquote|br|button|canvas|cite|code|command|datalist|del|details|dfn|dialog|div|dl|em|embed|fieldset|' +
2471 						'figure|footer|form|h1|h2|h3|h4|h5|h6|header|hgroup|hr|i|iframe|img|input|ins|kbd|keygen|label|link|map|mark|menu|meta|meter|nav|noscript|ol|object|output|' +
2472 						'p|pre|progress|q|ruby|samp|script|section|select|small|span|strong|style|sub|sup|svg|table|textarea|time|ul|var|video'
2473 				}, 'html[A|manifest][body|head]' +
2474 					'head[A][base|command|link|meta|noscript|script|style|title]' +
2475 					'title[A][#]' +
2476 					'base[A|href|target][]' +
2477 					'link[A|href|rel|media|type|sizes][]' +
2478 					'meta[A|http-equiv|name|content|charset][]' +
2479 					'style[A|type|media|scoped][#]' +
2480 					'script[A|charset|type|src|defer|async][#]' +
2481 					'noscript[A][C]' +
2482 					'body[A][C]' +
2483 					'section[A][C]' +
2484 					'nav[A][C]' +
2485 					'article[A][C]' +
2486 					'aside[A][C]' +
2487 					'h1[A][B]' +
2488 					'h2[A][B]' +
2489 					'h3[A][B]' +
2490 					'h4[A][B]' +
2491 					'h5[A][B]' +
2492 					'h6[A][B]' +
2493 					'hgroup[A][h1|h2|h3|h4|h5|h6]' +
2494 					'header[A][C]' +
2495 					'footer[A][C]' +
2496 					'address[A][C]' +
2497 					'p[A][B]' +
2498 					'br[A][]' +
2499 					'pre[A][B]' +
2500 					'dialog[A][dd|dt]' +
2501 					'blockquote[A|cite][C]' +
2502 					'ol[A|start|reversed][li]' +
2503 					'ul[A][li]' +
2504 					'li[A|value][C]' +
2505 					'dl[A][dd|dt]' +
2506 					'dt[A][B]' +
2507 					'dd[A][C]' +
2508 					'a[A|href|target|ping|rel|media|type][B]' +
2509 					'em[A][B]' +
2510 					'strong[A][B]' +
2511 					'small[A][B]' +
2512 					'cite[A][B]' +
2513 					'q[A|cite][B]' +
2514 					'dfn[A][B]' +
2515 					'abbr[A][B]' +
2516 					'code[A][B]' +
2517 					'var[A][B]' +
2518 					'samp[A][B]' +
2519 					'kbd[A][B]' +
2520 					'sub[A][B]' +
2521 					'sup[A][B]' +
2522 					'i[A][B]' +
2523 					'b[A][B]' +
2524 					'mark[A][B]' +
2525 					'progress[A|value|max][B]' +
2526 					'meter[A|value|min|max|low|high|optimum][B]' +
2527 					'time[A|datetime][B]' +
2528 					'ruby[A][B|rt|rp]' +
2529 					'rt[A][B]' +
2530 					'rp[A][B]' +
2531 					'bdo[A][B]' +
2532 					'span[A][B]' +
2533 					'ins[A|cite|datetime][B]' +
2534 					'del[A|cite|datetime][B]' +
2535 					'figure[A][C|legend|figcaption]' +
2536 					'figcaption[A][C]' +
2537 					'img[A|alt|src|height|width|usemap|ismap][]' +
2538 					'iframe[A|name|src|height|width|sandbox|seamless][]' +
2539 					'embed[A|src|height|width|type][]' +
2540 					'object[A|data|type|height|width|usemap|name|form|classid][param]' +
2541 					'param[A|name|value][]' +
2542 					'details[A|open][C|legend]' +
2543 					'command[A|type|label|icon|disabled|checked|radiogroup][]' +
2544 					'menu[A|type|label][C|li]' +
2545 					'legend[A][C|B]' +
2546 					'div[A][C]' +
2547 					'source[A|src|type|media][]' +
2548 					'audio[A|src|autobuffer|autoplay|loop|controls][source]' +
2549 					'video[A|src|autobuffer|autoplay|loop|controls|width|height|poster][source]' +
2550 					'hr[A][]' +
2551 					'form[A|accept-charset|action|autocomplete|enctype|method|name|novalidate|target][C]' +
2552 					'fieldset[A|disabled|form|name][C|legend]' +
2553 					'label[A|form|for][B]' +
2554 					'input[A|type|accept|alt|autocomplete|checked|disabled|form|formaction|formenctype|formmethod|formnovalidate|formtarget|height|list|max|maxlength|min|' +
2555 						'multiple|pattern|placeholder|readonly|required|size|src|step|width|files|value|name][]' +
2556 					'button[A|autofocus|disabled|form|formaction|formenctype|formmethod|formnovalidate|formtarget|name|value|type][B]' +
2557 					'select[A|autofocus|disabled|form|multiple|name|size][option|optgroup]' +
2558 					'datalist[A][B|option]' +
2559 					'optgroup[A|disabled|label][option]' +
2560 					'option[A|disabled|selected|label|value][]' +
2561 					'textarea[A|autofocus|disabled|form|maxlength|name|placeholder|readonly|required|rows|cols|wrap][]' +
2562 					'keygen[A|autofocus|challenge|disabled|form|keytype|name][]' +
2563 					'output[A|for|form|name][B]' +
2564 					'canvas[A|width|height][]' +
2565 					'map[A|name][B|C]' +
2566 					'area[A|shape|coords|href|alt|target|media|rel|ping|type][]' +
2567 					'mathml[A][]' +
2568 					'svg[A][]' +
2569 					'table[A|border][caption|colgroup|thead|tfoot|tbody|tr]' +
2570 					'caption[A][C]' +
2571 					'colgroup[A|span][col]' +
2572 					'col[A|span][]' +
2573 					'thead[A][tr]' +
2574 					'tfoot[A][tr]' +
2575 					'tbody[A][tr]' +
2576 					'tr[A][th|td]' +
2577 					'th[A|headers|rowspan|colspan|scope][B]' +
2578 					'td[A|headers|rowspan|colspan][C]' +
2579 					'wbr[A][]'
2580 			);
2581 		}
2582 
2583 		return html5;
2584 	};
2585 
2586 	function getHTML4() {
2587 		var html4 = mapCache.html4;
2588 
2589 		if (!html4) {
2590 			// This is the XHTML 1.0 transitional elements with it's attributes and children packed to reduce it's size
2591 			html4 = mapCache.html4 = unpack({
2592 				Z : 'H|K|N|O|P',
2593 				Y : 'X|form|R|Q',
2594 				ZG : 'E|span|width|align|char|charoff|valign',
2595 				X : 'p|T|div|U|W|isindex|fieldset|table',
2596 				ZF : 'E|align|char|charoff|valign',
2597 				W : 'pre|hr|blockquote|address|center|noframes',
2598 				ZE : 'abbr|axis|headers|scope|rowspan|colspan|align|char|charoff|valign|nowrap|bgcolor|width|height',
2599 				ZD : '[E][S]',
2600 				U : 'ul|ol|dl|menu|dir',
2601 				ZC : 'p|Y|div|U|W|table|br|span|bdo|object|applet|img|map|K|N|Q',
2602 				T : 'h1|h2|h3|h4|h5|h6',
2603 				ZB : 'X|S|Q',
2604 				S : 'R|P',
2605 				ZA : 'a|G|J|M|O|P',
2606 				R : 'a|H|K|N|O',
2607 				Q : 'noscript|P',
2608 				P : 'ins|del|script',
2609 				O : 'input|select|textarea|label|button',
2610 				N : 'M|L',
2611 				M : 'em|strong|dfn|code|q|samp|kbd|var|cite|abbr|acronym',
2612 				L : 'sub|sup',
2613 				K : 'J|I',
2614 				J : 'tt|i|b|u|s|strike',
2615 				I : 'big|small|font|basefont',
2616 				H : 'G|F',
2617 				G : 'br|span|bdo',
2618 				F : 'object|applet|img|map|iframe',
2619 				E : 'A|B|C',
2620 				D : 'accesskey|tabindex|onfocus|onblur',
2621 				C : 'onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup',
2622 				B : 'lang|xml:lang|dir',
2623 				A : 'id|class|style|title'
2624 			}, 'script[id|charset|type|language|src|defer|xml:space][]' + 
2625 				'style[B|id|type|media|title|xml:space][]' + 
2626 				'object[E|declare|classid|codebase|data|type|codetype|archive|standby|width|height|usemap|name|tabindex|align|border|hspace|vspace][#|param|Y]' + 
2627 				'param[id|name|value|valuetype|type][]' + 
2628 				'p[E|align][#|S]' + 
2629 				'a[E|D|charset|type|name|href|hreflang|rel|rev|shape|coords|target][#|Z]' + 
2630 				'br[A|clear][]' + 
2631 				'span[E][#|S]' + 
2632 				'bdo[A|C|B][#|S]' + 
2633 				'applet[A|codebase|archive|code|object|alt|name|width|height|align|hspace|vspace][#|param|Y]' + 
2634 				'h1[E|align][#|S]' + 
2635 				'img[E|src|alt|name|longdesc|width|height|usemap|ismap|align|border|hspace|vspace][]' + 
2636 				'map[B|C|A|name][X|form|Q|area]' + 
2637 				'h2[E|align][#|S]' + 
2638 				'iframe[A|longdesc|name|src|frameborder|marginwidth|marginheight|scrolling|align|width|height][#|Y]' + 
2639 				'h3[E|align][#|S]' + 
2640 				'tt[E][#|S]' + 
2641 				'i[E][#|S]' + 
2642 				'b[E][#|S]' + 
2643 				'u[E][#|S]' + 
2644 				's[E][#|S]' + 
2645 				'strike[E][#|S]' + 
2646 				'big[E][#|S]' + 
2647 				'small[E][#|S]' + 
2648 				'font[A|B|size|color|face][#|S]' + 
2649 				'basefont[id|size|color|face][]' + 
2650 				'em[E][#|S]' + 
2651 				'strong[E][#|S]' + 
2652 				'dfn[E][#|S]' + 
2653 				'code[E][#|S]' + 
2654 				'q[E|cite][#|S]' + 
2655 				'samp[E][#|S]' + 
2656 				'kbd[E][#|S]' + 
2657 				'var[E][#|S]' + 
2658 				'cite[E][#|S]' + 
2659 				'abbr[E][#|S]' + 
2660 				'acronym[E][#|S]' + 
2661 				'sub[E][#|S]' + 
2662 				'sup[E][#|S]' + 
2663 				'input[E|D|type|name|value|checked|disabled|readonly|size|maxlength|src|alt|usemap|onselect|onchange|accept|align][]' + 
2664 				'select[E|name|size|multiple|disabled|tabindex|onfocus|onblur|onchange][optgroup|option]' + 
2665 				'optgroup[E|disabled|label][option]' + 
2666 				'option[E|selected|disabled|label|value][]' + 
2667 				'textarea[E|D|name|rows|cols|disabled|readonly|onselect|onchange][]' + 
2668 				'label[E|for|accesskey|onfocus|onblur][#|S]' + 
2669 				'button[E|D|name|value|type|disabled][#|p|T|div|U|W|table|G|object|applet|img|map|K|N|Q]' + 
2670 				'h4[E|align][#|S]' + 
2671 				'ins[E|cite|datetime][#|Y]' + 
2672 				'h5[E|align][#|S]' + 
2673 				'del[E|cite|datetime][#|Y]' + 
2674 				'h6[E|align][#|S]' + 
2675 				'div[E|align][#|Y]' + 
2676 				'ul[E|type|compact][li]' + 
2677 				'li[E|type|value][#|Y]' + 
2678 				'ol[E|type|compact|start][li]' + 
2679 				'dl[E|compact][dt|dd]' + 
2680 				'dt[E][#|S]' + 
2681 				'dd[E][#|Y]' + 
2682 				'menu[E|compact][li]' + 
2683 				'dir[E|compact][li]' + 
2684 				'pre[E|width|xml:space][#|ZA]' + 
2685 				'hr[E|align|noshade|size|width][]' + 
2686 				'blockquote[E|cite][#|Y]' + 
2687 				'address[E][#|S|p]' + 
2688 				'center[E][#|Y]' + 
2689 				'noframes[E][#|Y]' + 
2690 				'isindex[A|B|prompt][]' + 
2691 				'fieldset[E][#|legend|Y]' + 
2692 				'legend[E|accesskey|align][#|S]' + 
2693 				'table[E|summary|width|border|frame|rules|cellspacing|cellpadding|align|bgcolor][caption|col|colgroup|thead|tfoot|tbody|tr]' + 
2694 				'caption[E|align][#|S]' + 
2695 				'col[ZG][]' + 
2696 				'colgroup[ZG][col]' + 
2697 				'thead[ZF][tr]' + 
2698 				'tr[ZF|bgcolor][th|td]' + 
2699 				'th[E|ZE][#|Y]' + 
2700 				'form[E|action|method|name|enctype|onsubmit|onreset|accept|accept-charset|target][#|X|R|Q]' + 
2701 				'noscript[E][#|Y]' + 
2702 				'td[E|ZE][#|Y]' + 
2703 				'tfoot[ZF][tr]' + 
2704 				'tbody[ZF][tr]' + 
2705 				'area[E|D|shape|coords|href|nohref|alt|target][]' + 
2706 				'base[id|href|target][]' + 
2707 				'body[E|onload|onunload|background|bgcolor|text|link|vlink|alink][#|Y]'
2708 			);
2709 		}
2710 
2711 		return html4;
2712 	};
2713 
2714 	tinymce.html.Schema = function(settings) {
2715 		var self = this, elements = {}, children = {}, patternElements = [], validStyles, schemaItems;
2716 		var whiteSpaceElementsMap, selfClosingElementsMap, shortEndedElementsMap, boolAttrMap, blockElementsMap, nonEmptyElementsMap, customElementsMap = {};
2717 
2718 		// Creates an lookup table map object for the specified option or the default value
2719 		function createLookupTable(option, default_value, extend) {
2720 			var value = settings[option];
2721 
2722 			if (!value) {
2723 				// Get cached default map or make it if needed
2724 				value = mapCache[option];
2725 
2726 				if (!value) {
2727 					value = makeMap(default_value, ' ', makeMap(default_value.toUpperCase(), ' '));
2728 					value = tinymce.extend(value, extend);
2729 
2730 					mapCache[option] = value;
2731 				}
2732 			} else {
2733 				// Create custom map
2734 				value = makeMap(value, ',', makeMap(value.toUpperCase(), ' '));
2735 			}
2736 
2737 			return value;
2738 		};
2739 
2740 		settings = settings || {};
2741 		schemaItems = settings.schema == "html5" ? getHTML5() : getHTML4();
2742 
2743 		// Allow all elements and attributes if verify_html is set to false
2744 		if (settings.verify_html === false)
2745 			settings.valid_elements = '*[*]';
2746 
2747 		// Build styles list
2748 		if (settings.valid_styles) {
2749 			validStyles = {};
2750 
2751 			// Convert styles into a rule list
2752 			each(settings.valid_styles, function(value, key) {
2753 				validStyles[key] = tinymce.explode(value);
2754 			});
2755 		}
2756 
2757 		// Setup map objects
2758 		whiteSpaceElementsMap = createLookupTable('whitespace_elements', 'pre script style textarea');
2759 		selfClosingElementsMap = createLookupTable('self_closing_elements', 'colgroup dd dt li option p td tfoot th thead tr');
2760 		shortEndedElementsMap = createLookupTable('short_ended_elements', 'area base basefont br col frame hr img input isindex link meta param embed source wbr');
2761 		boolAttrMap = createLookupTable('boolean_attributes', 'checked compact declare defer disabled ismap multiple nohref noresize noshade nowrap readonly selected autoplay loop controls');
2762 		nonEmptyElementsMap = createLookupTable('non_empty_elements', 'td th iframe video audio object', shortEndedElementsMap);
2763 		blockElementsMap = createLookupTable('block_elements', 'h1 h2 h3 h4 h5 h6 hr p div address pre form table tbody thead tfoot ' + 
2764 						'th tr td li ol ul caption blockquote center dl dt dd dir fieldset ' + 
2765 						'noscript menu isindex samp header footer article section hgroup aside nav figure option datalist select optgroup');
2766 
2767 		// Converts a wildcard expression string to a regexp for example *a will become /.*a/.
2768 		function patternToRegExp(str) {
2769 			return new RegExp('^' + str.replace(/([?+*])/g, '.$1') + '$');
2770 		};
2771 
2772 		// Parses the specified valid_elements string and adds to the current rules
2773 		// This function is a bit hard to read since it's heavily optimized for speed
2774 		function addValidElements(valid_elements) {
2775 			var ei, el, ai, al, yl, matches, element, attr, attrData, elementName, attrName, attrType, attributes, attributesOrder,
2776 				prefix, outputName, globalAttributes, globalAttributesOrder, transElement, key, childKey, value,
2777 				elementRuleRegExp = /^([#+\-])?([^\[\/]+)(?:\/([^\[]+))?(?:\[([^\]]+)\])?$/,
2778 				attrRuleRegExp = /^([!\-])?(\w+::\w+|[^=:<]+)?(?:([=:<])(.*))?$/,
2779 				hasPatternsRegExp = /[*?+]/;
2780 
2781 			if (valid_elements) {
2782 				// Split valid elements into an array with rules
2783 				valid_elements = split(valid_elements);
2784 
2785 				if (elements['@']) {
2786 					globalAttributes = elements['@'].attributes;
2787 					globalAttributesOrder = elements['@'].attributesOrder;
2788 				}
2789 
2790 				// Loop all rules
2791 				for (ei = 0, el = valid_elements.length; ei < el; ei++) {
2792 					// Parse element rule
2793 					matches = elementRuleRegExp.exec(valid_elements[ei]);
2794 					if (matches) {
2795 						// Setup local names for matches
2796 						prefix = matches[1];
2797 						elementName = matches[2];
2798 						outputName = matches[3];
2799 						attrData = matches[4];
2800 
2801 						// Create new attributes and attributesOrder
2802 						attributes = {};
2803 						attributesOrder = [];
2804 
2805 						// Create the new element
2806 						element = {
2807 							attributes : attributes,
2808 							attributesOrder : attributesOrder
2809 						};
2810 
2811 						// Padd empty elements prefix
2812 						if (prefix === '#')
2813 							element.paddEmpty = true;
2814 
2815 						// Remove empty elements prefix
2816 						if (prefix === '-')
2817 							element.removeEmpty = true;
2818 
2819 						// Copy attributes from global rule into current rule
2820 						if (globalAttributes) {
2821 							for (key in globalAttributes)
2822 								attributes[key] = globalAttributes[key];
2823 
2824 							attributesOrder.push.apply(attributesOrder, globalAttributesOrder);
2825 						}
2826 
2827 						// Attributes defined
2828 						if (attrData) {
2829 							attrData = split(attrData, '|');
2830 							for (ai = 0, al = attrData.length; ai < al; ai++) {
2831 								matches = attrRuleRegExp.exec(attrData[ai]);
2832 								if (matches) {
2833 									attr = {};
2834 									attrType = matches[1];
2835 									attrName = matches[2].replace(/::/g, ':');
2836 									prefix = matches[3];
2837 									value = matches[4];
2838 
2839 									// Required
2840 									if (attrType === '!') {
2841 										element.attributesRequired = element.attributesRequired || [];
2842 										element.attributesRequired.push(attrName);
2843 										attr.required = true;
2844 									}
2845 
2846 									// Denied from global
2847 									if (attrType === '-') {
2848 										delete attributes[attrName];
2849 										attributesOrder.splice(tinymce.inArray(attributesOrder, attrName), 1);
2850 										continue;
2851 									}
2852 
2853 									// Default value
2854 									if (prefix) {
2855 										// Default value
2856 										if (prefix === '=') {
2857 											element.attributesDefault = element.attributesDefault || [];
2858 											element.attributesDefault.push({name: attrName, value: value});
2859 											attr.defaultValue = value;
2860 										}
2861 
2862 										// Forced value
2863 										if (prefix === ':') {
2864 											element.attributesForced = element.attributesForced || [];
2865 											element.attributesForced.push({name: attrName, value: value});
2866 											attr.forcedValue = value;
2867 										}
2868 
2869 										// Required values
2870 										if (prefix === '<')
2871 											attr.validValues = makeMap(value, '?');
2872 									}
2873 
2874 									// Check for attribute patterns
2875 									if (hasPatternsRegExp.test(attrName)) {
2876 										element.attributePatterns = element.attributePatterns || [];
2877 										attr.pattern = patternToRegExp(attrName);
2878 										element.attributePatterns.push(attr);
2879 									} else {
2880 										// Add attribute to order list if it doesn't already exist
2881 										if (!attributes[attrName])
2882 											attributesOrder.push(attrName);
2883 
2884 										attributes[attrName] = attr;
2885 									}
2886 								}
2887 							}
2888 						}
2889 
2890 						// Global rule, store away these for later usage
2891 						if (!globalAttributes && elementName == '@') {
2892 							globalAttributes = attributes;
2893 							globalAttributesOrder = attributesOrder;
2894 						}
2895 
2896 						// Handle substitute elements such as b/strong
2897 						if (outputName) {
2898 							element.outputName = elementName;
2899 							elements[outputName] = element;
2900 						}
2901 
2902 						// Add pattern or exact element
2903 						if (hasPatternsRegExp.test(elementName)) {
2904 							element.pattern = patternToRegExp(elementName);
2905 							patternElements.push(element);
2906 						} else
2907 							elements[elementName] = element;
2908 					}
2909 				}
2910 			}
2911 		};
2912 
2913 		function setValidElements(valid_elements) {
2914 			elements = {};
2915 			patternElements = [];
2916 
2917 			addValidElements(valid_elements);
2918 
2919 			each(schemaItems, function(element, name) {
2920 				children[name] = element.children;
2921 			});
2922 		};
2923 
2924 		// Adds custom non HTML elements to the schema
2925 		function addCustomElements(custom_elements) {
2926 			var customElementRegExp = /^(~)?(.+)$/;
2927 
2928 			if (custom_elements) {
2929 				each(split(custom_elements), function(rule) {
2930 					var matches = customElementRegExp.exec(rule),
2931 						inline = matches[1] === '~',
2932 						cloneName = inline ? 'span' : 'div',
2933 						name = matches[2];
2934 
2935 					children[name] = children[cloneName];
2936 					customElementsMap[name] = cloneName;
2937 
2938 					// If it's not marked as inline then add it to valid block elements
2939 					if (!inline)
2940 						blockElementsMap[name] = {};
2941 
2942 					// Add custom elements at span/div positions
2943 					each(children, function(element, child) {
2944 						if (element[cloneName])
2945 							element[name] = element[cloneName];
2946 					});
2947 				});
2948 			}
2949 		};
2950 
2951 		// Adds valid children to the schema object
2952 		function addValidChildren(valid_children) {
2953 			var childRuleRegExp = /^([+\-]?)(\w+)\[([^\]]+)\]$/;
2954 
2955 			if (valid_children) {
2956 				each(split(valid_children), function(rule) {
2957 					var matches = childRuleRegExp.exec(rule), parent, prefix;
2958 
2959 					if (matches) {
2960 						prefix = matches[1];
2961 
2962 						// Add/remove items from default
2963 						if (prefix)
2964 							parent = children[matches[2]];
2965 						else
2966 							parent = children[matches[2]] = {'#comment' : {}};
2967 
2968 						parent = children[matches[2]];
2969 
2970 						each(split(matches[3], '|'), function(child) {
2971 							if (prefix === '-')
2972 								delete parent[child];
2973 							else
2974 								parent[child] = {};
2975 						});
2976 					}
2977 				});
2978 			}
2979 		};
2980 
2981 		function getElementRule(name) {
2982 			var element = elements[name], i;
2983 
2984 			// Exact match found
2985 			if (element)
2986 				return element;
2987 
2988 			// No exact match then try the patterns
2989 			i = patternElements.length;
2990 			while (i--) {
2991 				element = patternElements[i];
2992 
2993 				if (element.pattern.test(name))
2994 					return element;
2995 			}
2996 		};
2997 
2998 		if (!settings.valid_elements) {
2999 			// No valid elements defined then clone the elements from the schema spec
3000 			each(schemaItems, function(element, name) {
3001 				elements[name] = {
3002 					attributes : element.attributes,
3003 					attributesOrder : element.attributesOrder
3004 				};
3005 
3006 				children[name] = element.children;
3007 			});
3008 
3009 			// Switch these on HTML4
3010 			if (settings.schema != "html5") {
3011 				each(split('strong/b,em/i'), function(item) {
3012 					item = split(item, '/');
3013 					elements[item[1]].outputName = item[0];
3014 				});
3015 			}
3016 
3017 			// Add default alt attribute for images
3018 			elements.img.attributesDefault = [{name: 'alt', value: ''}];
3019 
3020 			// Remove these if they are empty by default
3021 			each(split('ol,ul,sub,sup,blockquote,span,font,a,table,tbody,tr,strong,em,b,i'), function(name) {
3022 				if (elements[name]) {
3023 					elements[name].removeEmpty = true;
3024 				}
3025 			});
3026 
3027 			// Padd these by default
3028 			each(split('p,h1,h2,h3,h4,h5,h6,th,td,pre,div,address,caption'), function(name) {
3029 				elements[name].paddEmpty = true;
3030 			});
3031 		} else
3032 			setValidElements(settings.valid_elements);
3033 
3034 		addCustomElements(settings.custom_elements);
3035 		addValidChildren(settings.valid_children);
3036 		addValidElements(settings.extended_valid_elements);
3037 
3038 		// Todo: Remove this when we fix list handling to be valid
3039 		addValidChildren('+ol[ul|ol],+ul[ul|ol]');
3040 
3041 		// Delete invalid elements
3042 		if (settings.invalid_elements) {
3043 			tinymce.each(tinymce.explode(settings.invalid_elements), function(item) {
3044 				if (elements[item])
3045 					delete elements[item];
3046 			});
3047 		}
3048 
3049 		// If the user didn't allow span only allow internal spans
3050 		if (!getElementRule('span'))
3051 			addValidElements('span[!data-mce-type|*]');
3052 
3053 		self.children = children;
3054 
3055 		self.styles = validStyles;
3056 
3057 		self.getBoolAttrs = function() {
3058 			return boolAttrMap;
3059 		};
3060 
3061 		self.getBlockElements = function() {
3062 			return blockElementsMap;
3063 		};
3064 
3065 		self.getShortEndedElements = function() {
3066 			return shortEndedElementsMap;
3067 		};
3068 
3069 		self.getSelfClosingElements = function() {
3070 			return selfClosingElementsMap;
3071 		};
3072 
3073 		self.getNonEmptyElements = function() {
3074 			return nonEmptyElementsMap;
3075 		};
3076 
3077 		self.getWhiteSpaceElements = function() {
3078 			return whiteSpaceElementsMap;
3079 		};
3080 
3081 		self.isValidChild = function(name, child) {
3082 			var parent = children[name];
3083 
3084 			return !!(parent && parent[child]);
3085 		};
3086 
3087 		self.isValid = function(name, attr) {
3088 			var attrPatterns, i, rule = getElementRule(name);
3089 
3090 			// Check if it's a valid element
3091 			if (rule) {
3092 				if (attr) {
3093 					// Check if attribute name exists
3094 					if (rule.attributes[attr]) {
3095 						return true;
3096 					}
3097 
3098 					// Check if attribute matches a regexp pattern
3099 					attrPatterns = rule.attributePatterns;
3100 					if (attrPatterns) {
3101 						i = attrPatterns.length;
3102 						while (i--) {
3103 							if (attrPatterns[i].pattern.test(name)) {
3104 								return true;
3105 							}
3106 						}
3107 					}
3108 				} else {
3109 					return true;
3110 				}
3111 			}
3112 
3113 			// No match
3114 			return false;
3115 		};
3116 		
3117 		self.getElementRule = getElementRule;
3118 
3119 		self.getCustomElements = function() {
3120 			return customElementsMap;
3121 		};
3122 
3123 		self.addValidElements = addValidElements;
3124 
3125 		self.setValidElements = setValidElements;
3126 
3127 		self.addCustomElements = addCustomElements;
3128 
3129 		self.addValidChildren = addValidChildren;
3130 	};
3131 })(tinymce);
3132 
3133 (function(tinymce) {
3134 	tinymce.html.SaxParser = function(settings, schema) {
3135 		var self = this, noop = function() {};
3136 
3137 		settings = settings || {};
3138 		self.schema = schema = schema || new tinymce.html.Schema();
3139 
3140 		if (settings.fix_self_closing !== false)
3141 			settings.fix_self_closing = true;
3142 
3143 		// Add handler functions from settings and setup default handlers
3144 		tinymce.each('comment cdata text start end pi doctype'.split(' '), function(name) {
3145 			if (name)
3146 				self[name] = settings[name] || noop;
3147 		});
3148 
3149 		self.parse = function(html) {
3150 			var self = this, matches, index = 0, value, endRegExp, stack = [], attrList, i, text, name, isInternalElement, removeInternalElements,
3151 				shortEndedElements, fillAttrsMap, isShortEnded, validate, elementRule, isValidElement, attr, attribsValue, invalidPrefixRegExp,
3152 				validAttributesMap, validAttributePatterns, attributesRequired, attributesDefault, attributesForced, selfClosing,
3153 				tokenRegExp, attrRegExp, specialElements, attrValue, idCount = 0, decode = tinymce.html.Entities.decode, fixSelfClosing, isIE;
3154 
3155 			function processEndTag(name) {
3156 				var pos, i;
3157 
3158 				// Find position of parent of the same type
3159 				pos = stack.length;
3160 				while (pos--) {
3161 					if (stack[pos].name === name)
3162 						break;						
3163 				}
3164 
3165 				// Found parent
3166 				if (pos >= 0) {
3167 					// Close all the open elements
3168 					for (i = stack.length - 1; i >= pos; i--) {
3169 						name = stack[i];
3170 
3171 						if (name.valid)
3172 							self.end(name.name);
3173 					}
3174 
3175 					// Remove the open elements from the stack
3176 					stack.length = pos;
3177 				}
3178 			};
3179 
3180 			function parseAttribute(match, name, value, val2, val3) {
3181 				var attrRule, i;
3182 
3183 				name = name.toLowerCase();
3184 				value = name in fillAttrsMap ? name : decode(value || val2 || val3 || ''); // Handle boolean attribute than value attribute
3185 
3186 				// Validate name and value
3187 				if (validate && !isInternalElement && name.indexOf('data-') !== 0) {
3188 					attrRule = validAttributesMap[name];
3189 
3190 					// Find rule by pattern matching
3191 					if (!attrRule && validAttributePatterns) {
3192 						i = validAttributePatterns.length;
3193 						while (i--) {
3194 							attrRule = validAttributePatterns[i];
3195 							if (attrRule.pattern.test(name))
3196 								break;
3197 						}
3198 
3199 						// No rule matched
3200 						if (i === -1)
3201 							attrRule = null;
3202 					}
3203 
3204 					// No attribute rule found
3205 					if (!attrRule)
3206 						return;
3207 
3208 					// Validate value
3209 					if (attrRule.validValues && !(value in attrRule.validValues))
3210 						return;
3211 				}
3212 
3213 				// Add attribute to list and map
3214 				attrList.map[name] = value;
3215 				attrList.push({
3216 					name: name,
3217 					value: value
3218 				});
3219 			};
3220 
3221 			// Precompile RegExps and map objects
3222 			tokenRegExp = new RegExp('<(?:' +
3223 				'(?:!--([\\w\\W]*?)-->)|' + // Comment
3224 				'(?:!\\[CDATA\\[([\\w\\W]*?)\\]\\]>)|' + // CDATA
3225 				'(?:!DOCTYPE([\\w\\W]*?)>)|' + // DOCTYPE
3226 				'(?:\\?([^\\s\\/<>]+) ?([\\w\\W]*?)[?/]>)|' + // PI
3227 				'(?:\\/([^>]+)>)|' + // End element
3228 				'(?:([A-Za-z0-9\\-\\:]+)((?:\\s+[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*|\\/|\\s+)>)' + // Start element
3229 			')', 'g');
3230 
3231 			attrRegExp = /([\w:\-]+)(?:\s*=\s*(?:(?:\"((?:\\.|[^\"])*)\")|(?:\'((?:\\.|[^\'])*)\')|([^>\s]+)))?/g;
3232 			specialElements = {
3233 				'script' : /<\/script[^>]*>/gi,
3234 				'style' : /<\/style[^>]*>/gi,
3235 				'noscript' : /<\/noscript[^>]*>/gi
3236 			};
3237 
3238 			// Setup lookup tables for empty elements and boolean attributes
3239 			shortEndedElements = schema.getShortEndedElements();
3240 			selfClosing = settings.self_closing_elements || schema.getSelfClosingElements();
3241 			fillAttrsMap = schema.getBoolAttrs();
3242 			validate = settings.validate;
3243 			removeInternalElements = settings.remove_internals;
3244 			fixSelfClosing = settings.fix_self_closing;
3245 			isIE = tinymce.isIE;
3246 			invalidPrefixRegExp = /^:/;
3247 
3248 			while (matches = tokenRegExp.exec(html)) {
3249 				// Text
3250 				if (index < matches.index)
3251 					self.text(decode(html.substr(index, matches.index - index)));
3252 
3253 				if (value = matches[6]) { // End element
3254 					value = value.toLowerCase();
3255 
3256 					// IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements
3257 					if (isIE && invalidPrefixRegExp.test(value))
3258 						value = value.substr(1);
3259 
3260 					processEndTag(value);
3261 				} else if (value = matches[7]) { // Start element
3262 					value = value.toLowerCase();
3263 
3264 					// IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements
3265 					if (isIE && invalidPrefixRegExp.test(value))
3266 						value = value.substr(1);
3267 
3268 					isShortEnded = value in shortEndedElements;
3269 
3270 					// Is self closing tag for example an <li> after an open <li>
3271 					if (fixSelfClosing && selfClosing[value] && stack.length > 0 && stack[stack.length - 1].name === value)
3272 						processEndTag(value);
3273 
3274 					// Validate element
3275 					if (!validate || (elementRule = schema.getElementRule(value))) {
3276 						isValidElement = true;
3277 
3278 						// Grab attributes map and patters when validation is enabled
3279 						if (validate) {
3280 							validAttributesMap = elementRule.attributes;
3281 							validAttributePatterns = elementRule.attributePatterns;
3282 						}
3283 
3284 						// Parse attributes
3285 						if (attribsValue = matches[8]) {
3286 							isInternalElement = attribsValue.indexOf('data-mce-type') !== -1; // Check if the element is an internal element
3287 
3288 							// If the element has internal attributes then remove it if we are told to do so
3289 							if (isInternalElement && removeInternalElements)
3290 								isValidElement = false;
3291 
3292 							attrList = [];
3293 							attrList.map = {};
3294 
3295 							attribsValue.replace(attrRegExp, parseAttribute);
3296 						} else {
3297 							attrList = [];
3298 							attrList.map = {};
3299 						}
3300 
3301 						// Process attributes if validation is enabled
3302 						if (validate && !isInternalElement) {
3303 							attributesRequired = elementRule.attributesRequired;
3304 							attributesDefault = elementRule.attributesDefault;
3305 							attributesForced = elementRule.attributesForced;
3306 
3307 							// Handle forced attributes
3308 							if (attributesForced) {
3309 								i = attributesForced.length;
3310 								while (i--) {
3311 									attr = attributesForced[i];
3312 									name = attr.name;
3313 									attrValue = attr.value;
3314 
3315 									if (attrValue === '{$uid}')
3316 										attrValue = 'mce_' + idCount++;
3317 
3318 									attrList.map[name] = attrValue;
3319 									attrList.push({name: name, value: attrValue});
3320 								}
3321 							}
3322 
3323 							// Handle default attributes
3324 							if (attributesDefault) {
3325 								i = attributesDefault.length;
3326 								while (i--) {
3327 									attr = attributesDefault[i];
3328 									name = attr.name;
3329 
3330 									if (!(name in attrList.map)) {
3331 										attrValue = attr.value;
3332 
3333 										if (attrValue === '{$uid}')
3334 											attrValue = 'mce_' + idCount++;
3335 
3336 										attrList.map[name] = attrValue;
3337 										attrList.push({name: name, value: attrValue});
3338 									}
3339 								}
3340 							}
3341 
3342 							// Handle required attributes
3343 							if (attributesRequired) {
3344 								i = attributesRequired.length;
3345 								while (i--) {
3346 									if (attributesRequired[i] in attrList.map)
3347 										break;
3348 								}
3349 
3350 								// None of the required attributes where found
3351 								if (i === -1)
3352 									isValidElement = false;
3353 							}
3354 
3355 							// Invalidate element if it's marked as bogus
3356 							if (attrList.map['data-mce-bogus'])
3357 								isValidElement = false;
3358 						}
3359 
3360 						if (isValidElement)
3361 							self.start(value, attrList, isShortEnded);
3362 					} else
3363 						isValidElement = false;
3364 
3365 					// Treat script, noscript and style a bit different since they may include code that looks like elements
3366 					if (endRegExp = specialElements[value]) {
3367 						endRegExp.lastIndex = index = matches.index + matches[0].length;
3368 
3369 						if (matches = endRegExp.exec(html)) {
3370 							if (isValidElement)
3371 								text = html.substr(index, matches.index - index);
3372 
3373 							index = matches.index + matches[0].length;
3374 						} else {
3375 							text = html.substr(index);
3376 							index = html.length;
3377 						}
3378 
3379 						if (isValidElement && text.length > 0)
3380 							self.text(text, true);
3381 
3382 						if (isValidElement)
3383 							self.end(value);
3384 
3385 						tokenRegExp.lastIndex = index;
3386 						continue;
3387 					}
3388 
3389 					// Push value on to stack
3390 					if (!isShortEnded) {
3391 						if (!attribsValue || attribsValue.indexOf('/') != attribsValue.length - 1)
3392 							stack.push({name: value, valid: isValidElement});
3393 						else if (isValidElement)
3394 							self.end(value);
3395 					}
3396 				} else if (value = matches[1]) { // Comment
3397 					self.comment(value);
3398 				} else if (value = matches[2]) { // CDATA
3399 					self.cdata(value);
3400 				} else if (value = matches[3]) { // DOCTYPE
3401 					self.doctype(value);
3402 				} else if (value = matches[4]) { // PI
3403 					self.pi(value, matches[5]);
3404 				}
3405 
3406 				index = matches.index + matches[0].length;
3407 			}
3408 
3409 			// Text
3410 			if (index < html.length)
3411 				self.text(decode(html.substr(index)));
3412 
3413 			// Close any open elements
3414 			for (i = stack.length - 1; i >= 0; i--) {
3415 				value = stack[i];
3416 
3417 				if (value.valid)
3418 					self.end(value.name);
3419 			}
3420 		};
3421 	}
3422 })(tinymce);
3423 
3424 (function(tinymce) {
3425 	var whiteSpaceRegExp = /^[ \t\r\n]*$/, typeLookup = {
3426 		'#text' : 3,
3427 		'#comment' : 8,
3428 		'#cdata' : 4,
3429 		'#pi' : 7,
3430 		'#doctype' : 10,
3431 		'#document-fragment' : 11
3432 	};
3433 
3434 	// Walks the tree left/right
3435 	function walk(node, root_node, prev) {
3436 		var sibling, parent, startName = prev ? 'lastChild' : 'firstChild', siblingName = prev ? 'prev' : 'next';
3437 
3438 		// Walk into nodes if it has a start
3439 		if (node[startName])
3440 			return node[startName];
3441 
3442 		// Return the sibling if it has one
3443 		if (node !== root_node) {
3444 			sibling = node[siblingName];
3445 
3446 			if (sibling)
3447 				return sibling;
3448 
3449 			// Walk up the parents to look for siblings
3450 			for (parent = node.parent; parent && parent !== root_node; parent = parent.parent) {
3451 				sibling = parent[siblingName];
3452 
3453 				if (sibling)
3454 					return sibling;
3455 			}
3456 		}
3457 	};
3458 
3459 	function Node(name, type) {
3460 		this.name = name;
3461 		this.type = type;
3462 
3463 		if (type === 1) {
3464 			this.attributes = [];
3465 			this.attributes.map = {};
3466 		}
3467 	}
3468 
3469 	tinymce.extend(Node.prototype, {
3470 		replace : function(node) {
3471 			var self = this;
3472 
3473 			if (node.parent)
3474 				node.remove();
3475 
3476 			self.insert(node, self);
3477 			self.remove();
3478 
3479 			return self;
3480 		},
3481 
3482 		attr : function(name, value) {
3483 			var self = this, attrs, i, undef;
3484 
3485 			if (typeof name !== "string") {
3486 				for (i in name)
3487 					self.attr(i, name[i]);
3488 
3489 				return self;
3490 			}
3491 
3492 			if (attrs = self.attributes) {
3493 				if (value !== undef) {
3494 					// Remove attribute
3495 					if (value === null) {
3496 						if (name in attrs.map) {
3497 							delete attrs.map[name];
3498 
3499 							i = attrs.length;
3500 							while (i--) {
3501 								if (attrs[i].name === name) {
3502 									attrs = attrs.splice(i, 1);
3503 									return self;
3504 								}
3505 							}
3506 						}
3507 
3508 						return self;
3509 					}
3510 
3511 					// Set attribute
3512 					if (name in attrs.map) {
3513 						// Set attribute
3514 						i = attrs.length;
3515 						while (i--) {
3516 							if (attrs[i].name === name) {
3517 								attrs[i].value = value;
3518 								break;
3519 							}
3520 						}
3521 					} else
3522 						attrs.push({name: name, value: value});
3523 
3524 					attrs.map[name] = value;
3525 
3526 					return self;
3527 				} else {
3528 					return attrs.map[name];
3529 				}
3530 			}
3531 		},
3532 
3533 		clone : function() {
3534 			var self = this, clone = new Node(self.name, self.type), i, l, selfAttrs, selfAttr, cloneAttrs;
3535 
3536 			// Clone element attributes
3537 			if (selfAttrs = self.attributes) {
3538 				cloneAttrs = [];
3539 				cloneAttrs.map = {};
3540 
3541 				for (i = 0, l = selfAttrs.length; i < l; i++) {
3542 					selfAttr = selfAttrs[i];
3543 
3544 					// Clone everything except id
3545 					if (selfAttr.name !== 'id') {
3546 						cloneAttrs[cloneAttrs.length] = {name: selfAttr.name, value: selfAttr.value};
3547 						cloneAttrs.map[selfAttr.name] = selfAttr.value;
3548 					}
3549 				}
3550 
3551 				clone.attributes = cloneAttrs;
3552 			}
3553 
3554 			clone.value = self.value;
3555 			clone.shortEnded = self.shortEnded;
3556 
3557 			return clone;
3558 		},
3559 
3560 		wrap : function(wrapper) {
3561 			var self = this;
3562 
3563 			self.parent.insert(wrapper, self);
3564 			wrapper.append(self);
3565 
3566 			return self;
3567 		},
3568 
3569 		unwrap : function() {
3570 			var self = this, node, next;
3571 
3572 			for (node = self.firstChild; node; ) {
3573 				next = node.next;
3574 				self.insert(node, self, true);
3575 				node = next;
3576 			}
3577 
3578 			self.remove();
3579 		},
3580 
3581 		remove : function() {
3582 			var self = this, parent = self.parent, next = self.next, prev = self.prev;
3583 
3584 			if (parent) {
3585 				if (parent.firstChild === self) {
3586 					parent.firstChild = next;
3587 
3588 					if (next)
3589 						next.prev = null;
3590 				} else {
3591 					prev.next = next;
3592 				}
3593 
3594 				if (parent.lastChild === self) {
3595 					parent.lastChild = prev;
3596 
3597 					if (prev)
3598 						prev.next = null;
3599 				} else {
3600 					next.prev = prev;
3601 				}
3602 
3603 				self.parent = self.next = self.prev = null;
3604 			}
3605 
3606 			return self;
3607 		},
3608 
3609 		append : function(node) {
3610 			var self = this, last;
3611 
3612 			if (node.parent)
3613 				node.remove();
3614 
3615 			last = self.lastChild;
3616 			if (last) {
3617 				last.next = node;
3618 				node.prev = last;
3619 				self.lastChild = node;
3620 			} else
3621 				self.lastChild = self.firstChild = node;
3622 
3623 			node.parent = self;
3624 
3625 			return node;
3626 		},
3627 
3628 		insert : function(node, ref_node, before) {
3629 			var parent;
3630 
3631 			if (node.parent)
3632 				node.remove();
3633 
3634 			parent = ref_node.parent || this;
3635 
3636 			if (before) {
3637 				if (ref_node === parent.firstChild)
3638 					parent.firstChild = node;
3639 				else
3640 					ref_node.prev.next = node;
3641 
3642 				node.prev = ref_node.prev;
3643 				node.next = ref_node;
3644 				ref_node.prev = node;
3645 			} else {
3646 				if (ref_node === parent.lastChild)
3647 					parent.lastChild = node;
3648 				else
3649 					ref_node.next.prev = node;
3650 
3651 				node.next = ref_node.next;
3652 				node.prev = ref_node;
3653 				ref_node.next = node;
3654 			}
3655 
3656 			node.parent = parent;
3657 
3658 			return node;
3659 		},
3660 
3661 		getAll : function(name) {
3662 			var self = this, node, collection = [];
3663 
3664 			for (node = self.firstChild; node; node = walk(node, self)) {
3665 				if (node.name === name)
3666 					collection.push(node);
3667 			}
3668 
3669 			return collection;
3670 		},
3671 
3672 		empty : function() {
3673 			var self = this, nodes, i, node;
3674 
3675 			// Remove all children
3676 			if (self.firstChild) {
3677 				nodes = [];
3678 
3679 				// Collect the children
3680 				for (node = self.firstChild; node; node = walk(node, self))
3681 					nodes.push(node);
3682 
3683 				// Remove the children
3684 				i = nodes.length;
3685 				while (i--) {
3686 					node = nodes[i];
3687 					node.parent = node.firstChild = node.lastChild = node.next = node.prev = null;
3688 				}
3689 			}
3690 
3691 			self.firstChild = self.lastChild = null;
3692 
3693 			return self;
3694 		},
3695 
3696 		isEmpty : function(elements) {
3697 			var self = this, node = self.firstChild, i, name;
3698 
3699 			if (node) {
3700 				do {
3701 					if (node.type === 1) {
3702 						// Ignore bogus elements
3703 						if (node.attributes.map['data-mce-bogus'])
3704 							continue;
3705 
3706 						// Keep empty elements like <img />
3707 						if (elements[node.name])
3708 							return false;
3709 
3710 						// Keep elements with data attributes or name attribute like <a name="1"></a>
3711 						i = node.attributes.length;
3712 						while (i--) {
3713 							name = node.attributes[i].name;
3714 							if (name === "name" || name.indexOf('data-') === 0)
3715 								return false;
3716 						}
3717 					}
3718 
3719 					// Keep comments
3720 					if (node.type === 8)
3721 						return false;
3722 					
3723 					// Keep non whitespace text nodes
3724 					if ((node.type === 3 && !whiteSpaceRegExp.test(node.value)))
3725 						return false;
3726 				} while (node = walk(node, self));
3727 			}
3728 
3729 			return true;
3730 		},
3731 
3732 		walk : function(prev) {
3733 			return walk(this, null, prev);
3734 		}
3735 	});
3736 
3737 	tinymce.extend(Node, {
3738 		create : function(name, attrs) {
3739 			var node, attrName;
3740 
3741 			// Create node
3742 			node = new Node(name, typeLookup[name] || 1);
3743 
3744 			// Add attributes if needed
3745 			if (attrs) {
3746 				for (attrName in attrs)
3747 					node.attr(attrName, attrs[attrName]);
3748 			}
3749 
3750 			return node;
3751 		}
3752 	});
3753 
3754 	tinymce.html.Node = Node;
3755 })(tinymce);
3756 
3757 (function(tinymce) {
3758 	var Node = tinymce.html.Node;
3759 
3760 	tinymce.html.DomParser = function(settings, schema) {
3761 		var self = this, nodeFilters = {}, attributeFilters = [], matchedNodes = {}, matchedAttributes = {};
3762 
3763 		settings = settings || {};
3764 		settings.validate = "validate" in settings ? settings.validate : true;
3765 		settings.root_name = settings.root_name || 'body';
3766 		self.schema = schema = schema || new tinymce.html.Schema();
3767 
3768 		function fixInvalidChildren(nodes) {
3769 			var ni, node, parent, parents, newParent, currentNode, tempNode, childNode, i,
3770 				childClone, nonEmptyElements, nonSplitableElements, sibling, nextNode;
3771 
3772 			nonSplitableElements = tinymce.makeMap('tr,td,th,tbody,thead,tfoot,table');
3773 			nonEmptyElements = schema.getNonEmptyElements();
3774 
3775 			for (ni = 0; ni < nodes.length; ni++) {
3776 				node = nodes[ni];
3777 
3778 				// Already removed
3779 				if (!node.parent)
3780 					continue;
3781 
3782 				// Get list of all parent nodes until we find a valid parent to stick the child into
3783 				parents = [node];
3784 				for (parent = node.parent; parent && !schema.isValidChild(parent.name, node.name) && !nonSplitableElements[parent.name]; parent = parent.parent)
3785 					parents.push(parent);
3786 
3787 				// Found a suitable parent
3788 				if (parent && parents.length > 1) {
3789 					// Reverse the array since it makes looping easier
3790 					parents.reverse();
3791 
3792 					// Clone the related parent and insert that after the moved node
3793 					newParent = currentNode = self.filterNode(parents[0].clone());
3794 
3795 					// Start cloning and moving children on the left side of the target node
3796 					for (i = 0; i < parents.length - 1; i++) {
3797 						if (schema.isValidChild(currentNode.name, parents[i].name)) {
3798 							tempNode = self.filterNode(parents[i].clone());
3799 							currentNode.append(tempNode);
3800 						} else
3801 							tempNode = currentNode;
3802 
3803 						for (childNode = parents[i].firstChild; childNode && childNode != parents[i + 1]; ) {
3804 							nextNode = childNode.next;
3805 							tempNode.append(childNode);
3806 							childNode = nextNode;
3807 						}
3808 
3809 						currentNode = tempNode;
3810 					}
3811 
3812 					if (!newParent.isEmpty(nonEmptyElements)) {
3813 						parent.insert(newParent, parents[0], true);
3814 						parent.insert(node, newParent);
3815 					} else {
3816 						parent.insert(node, parents[0], true);
3817 					}
3818 
3819 					// Check if the element is empty by looking through it's contents and special treatment for <p><br /></p>
3820 					parent = parents[0];
3821 					if (parent.isEmpty(nonEmptyElements) || parent.firstChild === parent.lastChild && parent.firstChild.name === 'br') {
3822 						parent.empty().remove();
3823 					}
3824 				} else if (node.parent) {
3825 					// If it's an LI try to find a UL/OL for it or wrap it
3826 					if (node.name === 'li') {
3827 						sibling = node.prev;
3828 						if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {
3829 							sibling.append(node);
3830 							continue;
3831 						}
3832 
3833 						sibling = node.next;
3834 						if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {
3835 							sibling.insert(node, sibling.firstChild, true);
3836 							continue;
3837 						}
3838 
3839 						node.wrap(self.filterNode(new Node('ul', 1)));
3840 						continue;
3841 					}
3842 
3843 					// Try wrapping the element in a DIV
3844 					if (schema.isValidChild(node.parent.name, 'div') && schema.isValidChild('div', node.name)) {
3845 						node.wrap(self.filterNode(new Node('div', 1)));
3846 					} else {
3847 						// We failed wrapping it, then remove or unwrap it
3848 						if (node.name === 'style' || node.name === 'script')
3849 							node.empty().remove();
3850 						else
3851 							node.unwrap();
3852 					}
3853 				}
3854 			}
3855 		};
3856 
3857 		self.filterNode = function(node) {
3858 			var i, name, list;
3859 
3860 			// Run element filters
3861 			if (name in nodeFilters) {
3862 				list = matchedNodes[name];
3863 
3864 				if (list)
3865 					list.push(node);
3866 				else
3867 					matchedNodes[name] = [node];
3868 			}
3869 
3870 			// Run attribute filters
3871 			i = attributeFilters.length;
3872 			while (i--) {
3873 				name = attributeFilters[i].name;
3874 
3875 				if (name in node.attributes.map) {
3876 					list = matchedAttributes[name];
3877 
3878 					if (list)
3879 						list.push(node);
3880 					else
3881 						matchedAttributes[name] = [node];
3882 				}
3883 			}
3884 
3885 			return node;
3886 		};
3887 
3888 		self.addNodeFilter = function(name, callback) {
3889 			tinymce.each(tinymce.explode(name), function(name) {
3890 				var list = nodeFilters[name];
3891 
3892 				if (!list)
3893 					nodeFilters[name] = list = [];
3894 
3895 				list.push(callback);
3896 			});
3897 		};
3898 
3899 		self.addAttributeFilter = function(name, callback) {
3900 			tinymce.each(tinymce.explode(name), function(name) {
3901 				var i;
3902 
3903 				for (i = 0; i < attributeFilters.length; i++) {
3904 					if (attributeFilters[i].name === name) {
3905 						attributeFilters[i].callbacks.push(callback);
3906 						return;
3907 					}
3908 				}
3909 
3910 				attributeFilters.push({name: name, callbacks: [callback]});
3911 			});
3912 		};
3913 
3914 		self.parse = function(html, args) {
3915 			var parser, rootNode, node, nodes, i, l, fi, fl, list, name, validate,
3916 				blockElements, startWhiteSpaceRegExp, invalidChildren = [], isInWhiteSpacePreservedElement,
3917 				endWhiteSpaceRegExp, allWhiteSpaceRegExp, isAllWhiteSpaceRegExp, whiteSpaceElements, children, nonEmptyElements, rootBlockName;
3918 
3919 			args = args || {};
3920 			matchedNodes = {};
3921 			matchedAttributes = {};
3922 			blockElements = tinymce.extend(tinymce.makeMap('script,style,head,html,body,title,meta,param'), schema.getBlockElements());
3923 			nonEmptyElements = schema.getNonEmptyElements();
3924 			children = schema.children;
3925 			validate = settings.validate;
3926 			rootBlockName = "forced_root_block" in args ? args.forced_root_block : settings.forced_root_block;
3927 
3928 			whiteSpaceElements = schema.getWhiteSpaceElements();
3929 			startWhiteSpaceRegExp = /^[ \t\r\n]+/;
3930 			endWhiteSpaceRegExp = /[ \t\r\n]+$/;
3931 			allWhiteSpaceRegExp = /[ \t\r\n]+/g;
3932 			isAllWhiteSpaceRegExp = /^[ \t\r\n]+$/;
3933 
3934 			function addRootBlocks() {
3935 				var node = rootNode.firstChild, next, rootBlockNode;
3936 
3937 				while (node) {
3938 					next = node.next;
3939 
3940 					if (node.type == 3 || (node.type == 1 && node.name !== 'p' && !blockElements[node.name] && !node.attr('data-mce-type'))) {
3941 						if (!rootBlockNode) {
3942 							// Create a new root block element
3943 							rootBlockNode = createNode(rootBlockName, 1);
3944 							rootNode.insert(rootBlockNode, node);
3945 							rootBlockNode.append(node);
3946 						} else
3947 							rootBlockNode.append(node);
3948 					} else {
3949 						rootBlockNode = null;
3950 					}
3951 
3952 					node = next;
3953 				};
3954 			};
3955 
3956 			function createNode(name, type) {
3957 				var node = new Node(name, type), list;
3958 
3959 				if (name in nodeFilters) {
3960 					list = matchedNodes[name];
3961 
3962 					if (list)
3963 						list.push(node);
3964 					else
3965 						matchedNodes[name] = [node];
3966 				}
3967 
3968 				return node;
3969 			};
3970 
3971 			function removeWhitespaceBefore(node) {
3972 				var textNode, textVal, sibling;
3973 
3974 				for (textNode = node.prev; textNode && textNode.type === 3; ) {
3975 					textVal = textNode.value.replace(endWhiteSpaceRegExp, '');
3976 
3977 					if (textVal.length > 0) {
3978 						textNode.value = textVal;
3979 						textNode = textNode.prev;
3980 					} else {
3981 						sibling = textNode.prev;
3982 						textNode.remove();
3983 						textNode = sibling;
3984 					}
3985 				}
3986 			};
3987 
3988 			function cloneAndExcludeBlocks(input) {
3989 				var name, output = {};
3990 
3991 				for (name in input) {
3992 					if (name !== 'li' && name != 'p') {
3993 						output[name] = input[name];
3994 					}
3995 				}
3996 
3997 				return output;
3998 			};
3999 
4000 			parser = new tinymce.html.SaxParser({
4001 				validate : validate,
4002 
4003 				// Exclude P and LI from DOM parsing since it's treated better by the DOM parser
4004 				self_closing_elements: cloneAndExcludeBlocks(schema.getSelfClosingElements()),
4005 
4006 				cdata: function(text) {
4007 					node.append(createNode('#cdata', 4)).value = text;
4008 				},
4009 
4010 				text: function(text, raw) {
4011 					var textNode;
4012 
4013 					// Trim all redundant whitespace on non white space elements
4014 					if (!isInWhiteSpacePreservedElement) {
4015 						text = text.replace(allWhiteSpaceRegExp, ' ');
4016 
4017 						if (node.lastChild && blockElements[node.lastChild.name])
4018 							text = text.replace(startWhiteSpaceRegExp, '');
4019 					}
4020 
4021 					// Do we need to create the node
4022 					if (text.length !== 0) {
4023 						textNode = createNode('#text', 3);
4024 						textNode.raw = !!raw;
4025 						node.append(textNode).value = text;
4026 					}
4027 				},
4028 
4029 				comment: function(text) {
4030 					node.append(createNode('#comment', 8)).value = text;
4031 				},
4032 
4033 				pi: function(name, text) {
4034 					node.append(createNode(name, 7)).value = text;
4035 					removeWhitespaceBefore(node);
4036 				},
4037 
4038 				doctype: function(text) {
4039 					var newNode;
4040 		
4041 					newNode = node.append(createNode('#doctype', 10));
4042 					newNode.value = text;
4043 					removeWhitespaceBefore(node);
4044 				},
4045 
4046 				start: function(name, attrs, empty) {
4047 					var newNode, attrFiltersLen, elementRule, textNode, attrName, text, sibling, parent;
4048 
4049 					elementRule = validate ? schema.getElementRule(name) : {};
4050 					if (elementRule) {
4051 						newNode = createNode(elementRule.outputName || name, 1);
4052 						newNode.attributes = attrs;
4053 						newNode.shortEnded = empty;
4054 
4055 						node.append(newNode);
4056 
4057 						// Check if node is valid child of the parent node is the child is
4058 						// unknown we don't collect it since it's probably a custom element
4059 						parent = children[node.name];
4060 						if (parent && children[newNode.name] && !parent[newNode.name])
4061 							invalidChildren.push(newNode);
4062 
4063 						attrFiltersLen = attributeFilters.length;
4064 						while (attrFiltersLen--) {
4065 							attrName = attributeFilters[attrFiltersLen].name;
4066 
4067 							if (attrName in attrs.map) {
4068 								list = matchedAttributes[attrName];
4069 
4070 								if (list)
4071 									list.push(newNode);
4072 								else
4073 									matchedAttributes[attrName] = [newNode];
4074 							}
4075 						}
4076 
4077 						// Trim whitespace before block
4078 						if (blockElements[name])
4079 							removeWhitespaceBefore(newNode);
4080 
4081 						// Change current node if the element wasn't empty i.e not <br /> or <img />
4082 						if (!empty)
4083 							node = newNode;
4084 
4085 						// Check if we are inside a whitespace preserved element
4086 						if (!isInWhiteSpacePreservedElement && whiteSpaceElements[name]) {
4087 							isInWhiteSpacePreservedElement = true;
4088 						}
4089 					}
4090 				},
4091 
4092 				end: function(name) {
4093 					var textNode, elementRule, text, sibling, tempNode;
4094 
4095 					elementRule = validate ? schema.getElementRule(name) : {};
4096 					if (elementRule) {
4097 						if (blockElements[name]) {
4098 							if (!isInWhiteSpacePreservedElement) {
4099 								// Trim whitespace of the first node in a block
4100 								textNode = node.firstChild;
4101 								if (textNode && textNode.type === 3) {
4102 									text = textNode.value.replace(startWhiteSpaceRegExp, '');
4103 
4104 									// Any characters left after trim or should we remove it
4105 									if (text.length > 0) {
4106 										textNode.value = text;
4107 										textNode = textNode.next;
4108 									} else {
4109 										sibling = textNode.next;
4110 										textNode.remove();
4111 										textNode = sibling;
4112 									}
4113 
4114 									// Remove any pure whitespace siblings
4115 									while (textNode && textNode.type === 3) {
4116 										text = textNode.value;
4117 										sibling = textNode.next;
4118 
4119 										if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) {
4120 											textNode.remove();
4121 											textNode = sibling;
4122 										}
4123 
4124 										textNode = sibling;
4125 									}
4126 								}
4127 
4128 								// Trim whitespace of the last node in a block
4129 								textNode = node.lastChild;
4130 								if (textNode && textNode.type === 3) {
4131 									text = textNode.value.replace(endWhiteSpaceRegExp, '');
4132 
4133 									// Any characters left after trim or should we remove it
4134 									if (text.length > 0) {
4135 										textNode.value = text;
4136 										textNode = textNode.prev;
4137 									} else {
4138 										sibling = textNode.prev;
4139 										textNode.remove();
4140 										textNode = sibling;
4141 									}
4142 
4143 									// Remove any pure whitespace siblings
4144 									while (textNode && textNode.type === 3) {
4145 										text = textNode.value;
4146 										sibling = textNode.prev;
4147 
4148 										if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) {
4149 											textNode.remove();
4150 											textNode = sibling;
4151 										}
4152 
4153 										textNode = sibling;
4154 									}
4155 								}
4156 							}
4157 
4158 							// Trim start white space
4159 							textNode = node.prev;
4160 							if (textNode && textNode.type === 3) {
4161 								text = textNode.value.replace(startWhiteSpaceRegExp, '');
4162 
4163 								if (text.length > 0)
4164 									textNode.value = text;
4165 								else
4166 									textNode.remove();
4167 							}
4168 						}
4169 
4170 						// Check if we exited a whitespace preserved element
4171 						if (isInWhiteSpacePreservedElement && whiteSpaceElements[name]) {
4172 							isInWhiteSpacePreservedElement = false;
4173 						}
4174 
4175 						// Handle empty nodes
4176 						if (elementRule.removeEmpty || elementRule.paddEmpty) {
4177 							if (node.isEmpty(nonEmptyElements)) {
4178 								if (elementRule.paddEmpty)
4179 									node.empty().append(new Node('#text', '3')).value = '\u00a0';
4180 								else {
4181 									// Leave nodes that have a name like <a name="name">
4182 									if (!node.attributes.map.name && !node.attributes.map.id) {
4183 										tempNode = node.parent;
4184 										node.empty().remove();
4185 										node = tempNode;
4186 										return;
4187 									}
4188 								}
4189 							}
4190 						}
4191 
4192 						node = node.parent;
4193 					}
4194 				}
4195 			}, schema);
4196 
4197 			rootNode = node = new Node(args.context || settings.root_name, 11);
4198 
4199 			parser.parse(html);
4200 
4201 			// Fix invalid children or report invalid children in a contextual parsing
4202 			if (validate && invalidChildren.length) {
4203 				if (!args.context)
4204 					fixInvalidChildren(invalidChildren);
4205 				else
4206 					args.invalid = true;
4207 			}
4208 
4209 			// Wrap nodes in the root into block elements if the root is body
4210 			if (rootBlockName && rootNode.name == 'body')
4211 				addRootBlocks();
4212 
4213 			// Run filters only when the contents is valid
4214 			if (!args.invalid) {
4215 				// Run node filters
4216 				for (name in matchedNodes) {
4217 					list = nodeFilters[name];
4218 					nodes = matchedNodes[name];
4219 
4220 					// Remove already removed children
4221 					fi = nodes.length;
4222 					while (fi--) {
4223 						if (!nodes[fi].parent)
4224 							nodes.splice(fi, 1);
4225 					}
4226 
4227 					for (i = 0, l = list.length; i < l; i++)
4228 						list[i](nodes, name, args);
4229 				}
4230 
4231 				// Run attribute filters
4232 				for (i = 0, l = attributeFilters.length; i < l; i++) {
4233 					list = attributeFilters[i];
4234 
4235 					if (list.name in matchedAttributes) {
4236 						nodes = matchedAttributes[list.name];
4237 
4238 						// Remove already removed children
4239 						fi = nodes.length;
4240 						while (fi--) {
4241 							if (!nodes[fi].parent)
4242 								nodes.splice(fi, 1);
4243 						}
4244 
4245 						for (fi = 0, fl = list.callbacks.length; fi < fl; fi++)
4246 							list.callbacks[fi](nodes, list.name, args);
4247 					}
4248 				}
4249 			}
4250 
4251 			return rootNode;
4252 		};
4253 
4254 		// Remove <br> at end of block elements Gecko and WebKit injects BR elements to
4255 		// make it possible to place the caret inside empty blocks. This logic tries to remove
4256 		// these elements and keep br elements that where intended to be there intact
4257 		if (settings.remove_trailing_brs) {
4258 			self.addNodeFilter('br', function(nodes, name) {
4259 				var i, l = nodes.length, node, blockElements = tinymce.extend({}, schema.getBlockElements()),
4260 					nonEmptyElements = schema.getNonEmptyElements(), parent, lastParent, prev, prevName;
4261 
4262 				// Remove brs from body element as well
4263 				blockElements.body = 1;
4264 
4265 				// Must loop forwards since it will otherwise remove all brs in <p>a<br><br><br></p>
4266 				for (i = 0; i < l; i++) {
4267 					node = nodes[i];
4268 					parent = node.parent;
4269 
4270 					if (blockElements[node.parent.name] && node === parent.lastChild) {
4271 						// Loop all nodes to the left of the current node and check for other BR elements
4272 						// excluding bookmarks since they are invisible
4273 						prev = node.prev;
4274 						while (prev) {
4275 							prevName = prev.name;
4276 
4277 							// Ignore bookmarks
4278 							if (prevName !== "span" || prev.attr('data-mce-type') !== 'bookmark') {
4279 								// Found a non BR element
4280 								if (prevName !== "br")
4281 									break;
4282 	
4283 								// Found another br it's a <br><br> structure then don't remove anything
4284 								if (prevName === 'br') {
4285 									node = null;
4286 									break;
4287 								}
4288 							}
4289 
4290 							prev = prev.prev;
4291 						}
4292 
4293 						if (node) {
4294 							node.remove();
4295 
4296 							// Is the parent to be considered empty after we removed the BR
4297 							if (parent.isEmpty(nonEmptyElements)) {
4298 								elementRule = schema.getElementRule(parent.name);
4299 
4300 								// Remove or padd the element depending on schema rule
4301 								if (elementRule) {
4302 									if (elementRule.removeEmpty)
4303 										parent.remove();
4304 									else if (elementRule.paddEmpty)
4305 										parent.empty().append(new tinymce.html.Node('#text', 3)).value = '\u00a0';
4306 								}
4307 							}
4308 						}
4309 					} else {
4310 						// Replaces BR elements inside inline elements like <p><b><i><br></i></b></p> so they become <p><b><i> </i></b></p> 
4311 						lastParent = node;
4312 						while (parent.firstChild === lastParent && parent.lastChild === lastParent) {
4313 							lastParent = parent;
4314 
4315 							if (blockElements[parent.name]) {
4316 								break;
4317 							}
4318 
4319 							parent = parent.parent;
4320 						}
4321 
4322 						if (lastParent === parent) {
4323 							textNode = new tinymce.html.Node('#text', 3);
4324 							textNode.value = '\u00a0';
4325 							node.replace(textNode);
4326 						}
4327 					}
4328 				}
4329 			});
4330 		}
4331 
4332 		// Force anchor names closed, unless the setting "allow_html_in_named_anchor" is explicitly included.
4333 		if (!settings.allow_html_in_named_anchor) {
4334 			self.addAttributeFilter('id,name', function(nodes, name) {
4335 				var i = nodes.length, sibling, prevSibling, parent, node;
4336 
4337 				while (i--) {
4338 					node = nodes[i];
4339 					if (node.name === 'a' && node.firstChild && !node.attr('href')) {
4340 						parent = node.parent;
4341 
4342 						// Move children after current node
4343 						sibling = node.lastChild;
4344 						do {
4345 							prevSibling = sibling.prev;
4346 							parent.insert(sibling, node);
4347 							sibling = prevSibling;
4348 						} while (sibling);
4349 					}
4350 				}
4351 			});
4352 		}
4353 	}
4354 })(tinymce);
4355 
4356 tinymce.html.Writer = function(settings) {
4357 	var html = [], indent, indentBefore, indentAfter, encode, htmlOutput;
4358 
4359 	settings = settings || {};
4360 	indent = settings.indent;
4361 	indentBefore = tinymce.makeMap(settings.indent_before || '');
4362 	indentAfter = tinymce.makeMap(settings.indent_after || '');
4363 	encode = tinymce.html.Entities.getEncodeFunc(settings.entity_encoding || 'raw', settings.entities);
4364 	htmlOutput = settings.element_format == "html";
4365 
4366 	return {
4367 		start: function(name, attrs, empty) {
4368 			var i, l, attr, value;
4369 
4370 			if (indent && indentBefore[name] && html.length > 0) {
4371 				value = html[html.length - 1];
4372 
4373 				if (value.length > 0 && value !== '\n')
4374 					html.push('\n');
4375 			}
4376 
4377 			html.push('<', name);
4378 
4379 			if (attrs) {
4380 				for (i = 0, l = attrs.length; i < l; i++) {
4381 					attr = attrs[i];
4382 					html.push(' ', attr.name, '="', encode(attr.value, true), '"');
4383 				}
4384 			}
4385 
4386 			if (!empty || htmlOutput)
4387 				html[html.length] = '>';
4388 			else
4389 				html[html.length] = ' />';
4390 
4391 			if (empty && indent && indentAfter[name] && html.length > 0) {
4392 				value = html[html.length - 1];
4393 
4394 				if (value.length > 0 && value !== '\n')
4395 					html.push('\n');
4396 			}
4397 		},
4398 
4399 		end: function(name) {
4400 			var value;
4401 
4402 			/*if (indent && indentBefore[name] && html.length > 0) {
4403 				value = html[html.length - 1];
4404 
4405 				if (value.length > 0 && value !== '\n')
4406 					html.push('\n');
4407 			}*/
4408 
4409 			html.push('</', name, '>');
4410 
4411 			if (indent && indentAfter[name] && html.length > 0) {
4412 				value = html[html.length - 1];
4413 
4414 				if (value.length > 0 && value !== '\n')
4415 					html.push('\n');
4416 			}
4417 		},
4418 
4419 		text: function(text, raw) {
4420 			if (text.length > 0)
4421 				html[html.length] = raw ? text : encode(text);
4422 		},
4423 
4424 		cdata: function(text) {
4425 			html.push('<![CDATA[', text, ']]>');
4426 		},
4427 
4428 		comment: function(text) {
4429 			html.push('<!--', text, '-->');
4430 		},
4431 
4432 		pi: function(name, text) {
4433 			if (text)
4434 				html.push('<?', name, ' ', text, '?>');
4435 			else
4436 				html.push('<?', name, '?>');
4437 
4438 			if (indent)
4439 				html.push('\n');
4440 		},
4441 
4442 		doctype: function(text) {
4443 			html.push('<!DOCTYPE', text, '>', indent ? '\n' : '');
4444 		},
4445 
4446 		reset: function() {
4447 			html.length = 0;
4448 		},
4449 
4450 		getContent: function() {
4451 			return html.join('').replace(/\n$/, '');
4452 		}
4453 	};
4454 };
4455 
4456 (function(tinymce) {
4457 	tinymce.html.Serializer = function(settings, schema) {
4458 		var self = this, writer = new tinymce.html.Writer(settings);
4459 
4460 		settings = settings || {};
4461 		settings.validate = "validate" in settings ? settings.validate : true;
4462 
4463 		self.schema = schema = schema || new tinymce.html.Schema();
4464 		self.writer = writer;
4465 
4466 		self.serialize = function(node) {
4467 			var handlers, validate;
4468 
4469 			validate = settings.validate;
4470 
4471 			handlers = {
4472 				// #text
4473 				3: function(node, raw) {
4474 					writer.text(node.value, node.raw);
4475 				},
4476 
4477 				// #comment
4478 				8: function(node) {
4479 					writer.comment(node.value);
4480 				},
4481 
4482 				// Processing instruction
4483 				7: function(node) {
4484 					writer.pi(node.name, node.value);
4485 				},
4486 
4487 				// Doctype
4488 				10: function(node) {
4489 					writer.doctype(node.value);
4490 				},
4491 
4492 				// CDATA
4493 				4: function(node) {
4494 					writer.cdata(node.value);
4495 				},
4496 
4497 				// Document fragment
4498 				11: function(node) {
4499 					if ((node = node.firstChild)) {
4500 						do {
4501 							walk(node);
4502 						} while (node = node.next);
4503 					}
4504 				}
4505 			};
4506 
4507 			writer.reset();
4508 
4509 			function walk(node) {
4510 				var handler = handlers[node.type], name, isEmpty, attrs, attrName, attrValue, sortedAttrs, i, l, elementRule;
4511 
4512 				if (!handler) {
4513 					name = node.name;
4514 					isEmpty = node.shortEnded;
4515 					attrs = node.attributes;
4516 
4517 					// Sort attributes
4518 					if (validate && attrs && attrs.length > 1) {
4519 						sortedAttrs = [];
4520 						sortedAttrs.map = {};
4521 
4522 						elementRule = schema.getElementRule(node.name);
4523 						for (i = 0, l = elementRule.attributesOrder.length; i < l; i++) {
4524 							attrName = elementRule.attributesOrder[i];
4525 
4526 							if (attrName in attrs.map) {
4527 								attrValue = attrs.map[attrName];
4528 								sortedAttrs.map[attrName] = attrValue;
4529 								sortedAttrs.push({name: attrName, value: attrValue});
4530 							}
4531 						}
4532 
4533 						for (i = 0, l = attrs.length; i < l; i++) {
4534 							attrName = attrs[i].name;
4535 
4536 							if (!(attrName in sortedAttrs.map)) {
4537 								attrValue = attrs.map[attrName];
4538 								sortedAttrs.map[attrName] = attrValue;
4539 								sortedAttrs.push({name: attrName, value: attrValue});
4540 							}
4541 						}
4542 
4543 						attrs = sortedAttrs;
4544 					}
4545 
4546 					writer.start(node.name, attrs, isEmpty);
4547 
4548 					if (!isEmpty) {
4549 						if ((node = node.firstChild)) {
4550 							do {
4551 								walk(node);
4552 							} while (node = node.next);
4553 						}
4554 
4555 						writer.end(name);
4556 					}
4557 				} else
4558 					handler(node);
4559 			}
4560 
4561 			// Serialize element and treat all non elements as fragments
4562 			if (node.type == 1 && !settings.inner)
4563 				walk(node);
4564 			else
4565 				handlers[11](node);
4566 
4567 			return writer.getContent();
4568 		};
4569 	}
4570 })(tinymce);
4571 
4572 // JSLint defined globals
4573 /*global tinymce:false, window:false */
4574 
4575 tinymce.dom = {};
4576 
4577 (function(namespace, expando) {
4578 	var w3cEventModel = !!document.addEventListener;
4579 
4580 	function addEvent(target, name, callback, capture) {
4581 		if (target.addEventListener) {
4582 			target.addEventListener(name, callback, capture || false);
4583 		} else if (target.attachEvent) {
4584 			target.attachEvent('on' + name, callback);
4585 		}
4586 	}
4587 
4588 	function removeEvent(target, name, callback, capture) {
4589 		if (target.removeEventListener) {
4590 			target.removeEventListener(name, callback, capture || false);
4591 		} else if (target.detachEvent) {
4592 			target.detachEvent('on' + name, callback);
4593 		}
4594 	}
4595 
4596 	function fix(original_event, data) {
4597 		var name, event = data || {};
4598 
4599 		// Dummy function that gets replaced on the delegation state functions
4600 		function returnFalse() {
4601 			return false;
4602 		}
4603 
4604 		// Dummy function that gets replaced on the delegation state functions
4605 		function returnTrue() {
4606 			return true;
4607 		}
4608 
4609 		// Copy all properties from the original event
4610 		for (name in original_event) {
4611 			// layerX/layerY is deprecated in Chrome and produces a warning
4612 			if (name !== "layerX" && name !== "layerY") {
4613 				event[name] = original_event[name];
4614 			}
4615 		}
4616 
4617 		// Normalize target IE uses srcElement
4618 		if (!event.target) {
4619 			event.target = event.srcElement || document;
4620 		}
4621 
4622 		// Add preventDefault method
4623 		event.preventDefault = function() {
4624 			event.isDefaultPrevented = returnTrue;
4625 
4626 			// Execute preventDefault on the original event object
4627 			if (original_event) {
4628 				if (original_event.preventDefault) {
4629 					original_event.preventDefault();
4630 				} else {
4631 					original_event.returnValue = false; // IE
4632 				}
4633 			}
4634 		};
4635 
4636 		// Add stopPropagation
4637 		event.stopPropagation = function() {
4638 			event.isPropagationStopped = returnTrue;
4639 
4640 			// Execute stopPropagation on the original event object
4641 			if (original_event) {
4642 				if (original_event.stopPropagation) {
4643 					original_event.stopPropagation();
4644 				} else {
4645 					original_event.cancelBubble = true; // IE
4646 				}
4647 			 }
4648 		};
4649 
4650 		// Add stopImmediatePropagation
4651 		event.stopImmediatePropagation = function() {
4652 			event.isImmediatePropagationStopped = returnTrue;
4653 			event.stopPropagation();
4654 		};
4655 
4656 		// Add event delegation states
4657 		if (!event.isDefaultPrevented) {
4658 			event.isDefaultPrevented = returnFalse;
4659 			event.isPropagationStopped = returnFalse;
4660 			event.isImmediatePropagationStopped = returnFalse;
4661 		}
4662 
4663 		return event;
4664 	}
4665 
4666 	function bindOnReady(win, callback, event_utils) {
4667 		var doc = win.document, event = {type: 'ready'};
4668 
4669 		// Gets called when the DOM is ready
4670 		function readyHandler() {
4671 			if (!event_utils.domLoaded) {
4672 				event_utils.domLoaded = true;
4673 				callback(event);
4674 			}
4675 		}
4676 
4677 		// Use W3C method
4678 		if (w3cEventModel) {
4679 			addEvent(win, 'DOMContentLoaded', readyHandler);
4680 		} else {
4681 			// Use IE method
4682 			addEvent(doc, "readystatechange", function() {
4683 				if (doc.readyState === "complete") {
4684 					removeEvent(doc, "readystatechange", arguments.callee);
4685 					readyHandler();
4686 				}
4687 			});
4688 
4689 			// Wait until we can scroll, when we can the DOM is initialized
4690 			if (doc.documentElement.doScroll && win === win.top) {
4691 				(function() {
4692 					try {
4693 						// If IE is used, use the trick by Diego Perini licensed under MIT by request to the author.
4694 						// http://javascript.nwbox.com/IEContentLoaded/
4695 						doc.documentElement.doScroll("left");
4696 					} catch (ex) {
4697 						setTimeout(arguments.callee, 0);
4698 						return;
4699 					}
4700 
4701 					readyHandler();
4702 				})();
4703 			}
4704 		}
4705 
4706 		// Fallback if any of the above methods should fail for some odd reason
4707 		addEvent(win, 'load', readyHandler);
4708 	}
4709 
4710 	function EventUtils(proxy) {
4711 		var self = this, events = {}, count, isFocusBlurBound, hasFocusIn, hasMouseEnterLeave, mouseEnterLeave;
4712 
4713 		hasMouseEnterLeave = "onmouseenter" in document.documentElement;
4714 		hasFocusIn = "onfocusin" in document.documentElement;
4715 		mouseEnterLeave = {mouseenter: 'mouseover', mouseleave: 'mouseout'};
4716 		count = 1;
4717 
4718 		// State if the DOMContentLoaded was executed or not
4719 		self.domLoaded = false;
4720 		self.events = events;
4721 
4722 		function executeHandlers(evt, id) {
4723 			var callbackList, i, l, callback;
4724 
4725 			callbackList = events[id][evt.type];
4726 			if (callbackList) {
4727 				for (i = 0, l = callbackList.length; i < l; i++) {
4728 					callback = callbackList[i];
4729 					
4730 					// Check if callback exists might be removed if a unbind is called inside the callback
4731 					if (callback && callback.func.call(callback.scope, evt) === false) {
4732 						evt.preventDefault();
4733 					}
4734 
4735 					// Should we stop propagation to immediate listeners
4736 					if (evt.isImmediatePropagationStopped()) {
4737 						return;
4738 					}
4739 				}
4740 			}
4741 		}
4742 
4743 		self.bind = function(target, names, callback, scope) {
4744 			var id, callbackList, i, name, fakeName, nativeHandler, capture, win = window;
4745 
4746 			// Native event handler function patches the event and executes the callbacks for the expando
4747 			function defaultNativeHandler(evt) {
4748 				executeHandlers(fix(evt || win.event), id);
4749 			}
4750 
4751 			// Don't bind to text nodes or comments
4752 			if (!target || target.nodeType === 3 || target.nodeType === 8) {
4753 				return;
4754 			}
4755 
4756 			// Create or get events id for the target
4757 			if (!target[expando]) {
4758 				id = count++;
4759 				target[expando] = id;
4760 				events[id] = {};
4761 			} else {
4762 				id = target[expando];
4763 
4764 				if (!events[id]) {
4765 					events[id] = {};
4766 				}
4767 			}
4768 
4769 			// Setup the specified scope or use the target as a default
4770 			scope = scope || target;
4771 
4772 			// Split names and bind each event, enables you to bind multiple events with one call
4773 			names = names.split(' ');
4774 			i = names.length;
4775 			while (i--) {
4776 				name = names[i];
4777 				nativeHandler = defaultNativeHandler;
4778 				fakeName = capture = false;
4779 
4780 				// Use ready instead of DOMContentLoaded
4781 				if (name === "DOMContentLoaded") {
4782 					name = "ready";
4783 				}
4784 
4785 				// DOM is already ready
4786 				if ((self.domLoaded || target.readyState == 'complete') && name === "ready") {
4787 					self.domLoaded = true;
4788 					callback.call(scope, fix({type: name}));
4789 					continue;
4790 				}
4791 
4792 				// Handle mouseenter/mouseleaver
4793 				if (!hasMouseEnterLeave) {
4794 					fakeName = mouseEnterLeave[name];
4795 
4796 					if (fakeName) {
4797 						nativeHandler = function(evt) {
4798 							var current, related;
4799 
4800 							current = evt.currentTarget;
4801 							related = evt.relatedTarget;
4802 
4803 							// Check if related is inside the current target if it's not then the event should be ignored since it's a mouseover/mouseout inside the element
4804 							if (related && current.contains) {
4805 								// Use contains for performance
4806 								related = current.contains(related);
4807 							} else {
4808 								while (related && related !== current) {
4809 									related = related.parentNode;
4810 								}
4811 							}
4812 
4813 							// Fire fake event
4814 							if (!related) {
4815 								evt = fix(evt || win.event);
4816 								evt.type = evt.type === 'mouseout' ? 'mouseleave' : 'mouseenter';
4817 								evt.target = current;
4818 								executeHandlers(evt, id);
4819 							}
4820 						};
4821 					}
4822 				}
4823 
4824 				// Fake bubbeling of focusin/focusout
4825 				if (!hasFocusIn && (name === "focusin" || name === "focusout")) {
4826 					capture = true;
4827 					fakeName = name === "focusin" ? "focus" : "blur";
4828 					nativeHandler = function(evt) {
4829 						evt = fix(evt || win.event);
4830 						evt.type = evt.type === 'focus' ? 'focusin' : 'focusout';
4831 						executeHandlers(evt, id);
4832 					};
4833 				}
4834 
4835 				// Setup callback list and bind native event
4836 				callbackList = events[id][name];
4837 				if (!callbackList) {
4838 					events[id][name] = callbackList = [{func: callback, scope: scope}];
4839 					callbackList.fakeName = fakeName;
4840 					callbackList.capture = capture;
4841 
4842 					// Add the nativeHandler to the callback list so that we can later unbind it
4843 					callbackList.nativeHandler = nativeHandler;
4844 					if (!w3cEventModel) {
4845 						callbackList.proxyHandler = proxy(id);
4846 					}
4847 
4848 					// Check if the target has native events support
4849 					if (name === "ready") {
4850 						bindOnReady(target, nativeHandler, self);
4851 					} else {
4852 						addEvent(target, fakeName || name, w3cEventModel ? nativeHandler : callbackList.proxyHandler, capture);
4853 					}
4854 				} else {
4855 					// If it already has an native handler then just push the callback
4856 					callbackList.push({func: callback, scope: scope});
4857 				}
4858 			}
4859 
4860 			target = callbackList = 0; // Clean memory for IE
4861 
4862 			return callback;
4863 		};
4864 
4865 		self.unbind = function(target, names, callback) {
4866 			var id, callbackList, i, ci, name, eventMap;
4867 
4868 			// Don't bind to text nodes or comments
4869 			if (!target || target.nodeType === 3 || target.nodeType === 8) {
4870 				return self;
4871 			}
4872 
4873 			// Unbind event or events if the target has the expando
4874 			id = target[expando];
4875 			if (id) {
4876 				eventMap = events[id];
4877 
4878 				// Specific callback
4879 				if (names) {
4880 					names = names.split(' ');
4881 					i = names.length;
4882 					while (i--) {
4883 						name = names[i];
4884 						callbackList = eventMap[name];
4885 
4886 						// Unbind the event if it exists in the map
4887 						if (callbackList) {
4888 							// Remove specified callback
4889 							if (callback) {
4890 								ci = callbackList.length;
4891 								while (ci--) {
4892 									if (callbackList[ci].func === callback) {
4893 										callbackList.splice(ci, 1);
4894 									}
4895 								}
4896 							}
4897 
4898 							// Remove all callbacks if there isn't a specified callback or there is no callbacks left
4899 							if (!callback || callbackList.length === 0) {
4900 								delete eventMap[name];
4901 								removeEvent(target, callbackList.fakeName || name, w3cEventModel ? callbackList.nativeHandler : callbackList.proxyHandler, callbackList.capture);
4902 							}
4903 						}
4904 					}
4905 				} else {
4906 					// All events for a specific element
4907 					for (name in eventMap) {
4908 						callbackList = eventMap[name];
4909 						removeEvent(target, callbackList.fakeName || name, w3cEventModel ? callbackList.nativeHandler : callbackList.proxyHandler, callbackList.capture);
4910 					}
4911 
4912 					eventMap = {};
4913 				}
4914 
4915 				// Check if object is empty, if it isn't then we won't remove the expando map
4916 				for (name in eventMap) {
4917 					return self;
4918 				}
4919 
4920 				// Delete event object
4921 				delete events[id];
4922 
4923 				// Remove expando from target
4924 				try {
4925 					// IE will fail here since it can't delete properties from window
4926 					delete target[expando];
4927 				} catch (ex) {
4928 					// IE will set it to null
4929 					target[expando] = null;
4930 				}
4931 			}
4932 
4933 			return self;
4934 		};
4935 
4936 		self.fire = function(target, name, args) {
4937 			var id, event;
4938 
4939 			// Don't bind to text nodes or comments
4940 			if (!target || target.nodeType === 3 || target.nodeType === 8) {
4941 				return self;
4942 			}
4943 
4944 			// Build event object by patching the args
4945 			event = fix(null, args);
4946 			event.type = name;
4947 
4948 			do {
4949 				// Found an expando that means there is listeners to execute
4950 				id = target[expando];
4951 				if (id) {
4952 					executeHandlers(event, id);
4953 				}
4954 
4955 				// Walk up the DOM
4956 				target = target.parentNode || target.ownerDocument || target.defaultView || target.parentWindow;
4957 			} while (target && !event.isPropagationStopped());
4958 
4959 			return self;
4960 		};
4961 
4962 		self.clean = function(target) {
4963 			var i, children, unbind = self.unbind;
4964 	
4965 			// Don't bind to text nodes or comments
4966 			if (!target || target.nodeType === 3 || target.nodeType === 8) {
4967 				return self;
4968 			}
4969 
4970 			// Unbind any element on the specificed target
4971 			if (target[expando]) {
4972 				unbind(target);
4973 			}
4974 
4975 			// Target doesn't have getElementsByTagName it's probably a window object then use it's document to find the children
4976 			if (!target.getElementsByTagName) {
4977 				target = target.document;
4978 			}
4979 
4980 			// Remove events from each child element
4981 			if (target && target.getElementsByTagName) {
4982 				unbind(target);
4983 
4984 				children = target.getElementsByTagName('*');
4985 				i = children.length;
4986 				while (i--) {
4987 					target = children[i];
4988 
4989 					if (target[expando]) {
4990 						unbind(target);
4991 					}
4992 				}
4993 			}
4994 
4995 			return self;
4996 		};
4997 
4998 		self.callNativeHandler = function(id, evt) {
4999 			if (events) {
5000 				events[id][evt.type].nativeHandler(evt);
5001 			}
5002 		};
5003 
5004 		self.destory = function() {
5005 			events = {};
5006 		};
5007 
5008 		// Legacy function calls
5009 
5010 		self.add = function(target, events, func, scope) {
5011 			// Old API supported direct ID assignment
5012 			if (typeof(target) === "string") {
5013 				target = document.getElementById(target);
5014 			}
5015 
5016 			// Old API supported multiple targets
5017 			if (target && target instanceof Array) {
5018 				var i = target.length;
5019 
5020 				while (i--) {
5021 					self.add(target[i], events, func, scope);
5022 				}
5023 
5024 				return;
5025 			}
5026 
5027 			// Old API called ready init
5028 			if (events === "init") {
5029 				events = "ready";
5030 			}
5031 
5032 			return self.bind(target, events instanceof Array ? events.join(' ') : events, func, scope);
5033 		};
5034 
5035 		self.remove = function(target, events, func, scope) {
5036 			if (!target) {
5037 				return self;
5038 			}
5039 
5040 			// Old API supported direct ID assignment
5041 			if (typeof(target) === "string") {
5042 				target = document.getElementById(target);
5043 			}
5044 
5045 			// Old API supported multiple targets
5046 			if (target instanceof Array) {
5047 				var i = target.length;
5048 
5049 				while (i--) {
5050 					self.remove(target[i], events, func, scope);
5051 				}
5052 
5053 				return self;
5054 			}
5055 
5056 			return self.unbind(target, events instanceof Array ? events.join(' ') : events, func);
5057 		};
5058 
5059 		self.clear = function(target) {
5060 			// Old API supported direct ID assignment
5061 			if (typeof(target) === "string") {
5062 				target = document.getElementById(target);
5063 			}
5064 
5065 			return self.clean(target);
5066 		};
5067 
5068 		self.cancel = function(e) {
5069 			if (e) {
5070 				self.prevent(e);
5071 				self.stop(e);
5072 			}
5073 
5074 			return false;
5075 		};
5076 
5077 		self.prevent = function(e) {
5078 			if (!e.preventDefault) {
5079 				e = fix(e);
5080 			}
5081 
5082 			e.preventDefault();
5083 
5084 			return false;
5085 		};
5086 
5087 		self.stop = function(e) {
5088 			if (!e.stopPropagation) {
5089 				e = fix(e);
5090 			}
5091 
5092 			e.stopPropagation();
5093 
5094 			return false;
5095 		};
5096 	}
5097 
5098 	namespace.EventUtils = EventUtils;
5099 
5100 	namespace.Event = new EventUtils(function(id) {
5101 		return function(evt) {
5102 			tinymce.dom.Event.callNativeHandler(id, evt);
5103 		};
5104 	});
5105 
5106 	// Bind ready event when tinymce script is loaded
5107 	namespace.Event.bind(window, 'ready', function() {});
5108 
5109 	namespace = 0;
5110 })(tinymce.dom, 'data-mce-expando'); // Namespace and expando
5111 
5112 tinymce.dom.TreeWalker = function(start_node, root_node) {
5113 	var node = start_node;
5114 
5115 	function findSibling(node, start_name, sibling_name, shallow) {
5116 		var sibling, parent;
5117 
5118 		if (node) {
5119 			// Walk into nodes if it has a start
5120 			if (!shallow && node[start_name])
5121 				return node[start_name];
5122 
5123 			// Return the sibling if it has one
5124 			if (node != root_node) {
5125 				sibling = node[sibling_name];
5126 				if (sibling)
5127 					return sibling;
5128 
5129 				// Walk up the parents to look for siblings
5130 				for (parent = node.parentNode; parent && parent != root_node; parent = parent.parentNode) {
5131 					sibling = parent[sibling_name];
5132 					if (sibling)
5133 						return sibling;
5134 				}
5135 			}
5136 		}
5137 	};
5138 
5139 	this.current = function() {
5140 		return node;
5141 	};
5142 
5143 	this.next = function(shallow) {
5144 		return (node = findSibling(node, 'firstChild', 'nextSibling', shallow));
5145 	};
5146 
5147 	this.prev = function(shallow) {
5148 		return (node = findSibling(node, 'lastChild', 'previousSibling', shallow));
5149 	};
5150 };
5151 
5152 (function(tinymce) {
5153 	// Shorten names
5154 	var each = tinymce.each,
5155 		is = tinymce.is,
5156 		isWebKit = tinymce.isWebKit,
5157 		isIE = tinymce.isIE,
5158 		Entities = tinymce.html.Entities,
5159 		simpleSelectorRe = /^([a-z0-9],?)+$/i,
5160 		whiteSpaceRegExp = /^[ \t\r\n]*$/;
5161 
5162 	tinymce.create('tinymce.dom.DOMUtils', {
5163 		doc : null,
5164 		root : null,
5165 		files : null,
5166 		pixelStyles : /^(top|left|bottom|right|width|height|borderWidth)$/,
5167 		props : {
5168 			"for" : "htmlFor",
5169 			"class" : "className",
5170 			className : "className",
5171 			checked : "checked",
5172 			disabled : "disabled",
5173 			maxlength : "maxLength",
5174 			readonly : "readOnly",
5175 			selected : "selected",
5176 			value : "value",
5177 			id : "id",
5178 			name : "name",
5179 			type : "type"
5180 		},
5181 
5182 		DOMUtils : function(d, s) {
5183 			var t = this, globalStyle, name, blockElementsMap;
5184 
5185 			t.doc = d;
5186 			t.win = window;
5187 			t.files = {};
5188 			t.cssFlicker = false;
5189 			t.counter = 0;
5190 			t.stdMode = !tinymce.isIE || d.documentMode >= 8;
5191 			t.boxModel = !tinymce.isIE || d.compatMode == "CSS1Compat" || t.stdMode;
5192 			t.hasOuterHTML = "outerHTML" in d.createElement("a");
5193 
5194 			t.settings = s = tinymce.extend({
5195 				keep_values : false,
5196 				hex_colors : 1
5197 			}, s);
5198 			
5199 			t.schema = s.schema;
5200 			t.styles = new tinymce.html.Styles({
5201 				url_converter : s.url_converter,
5202 				url_converter_scope : s.url_converter_scope
5203 			}, s.schema);
5204 
5205 			// Fix IE6SP2 flicker and check it failed for pre SP2
5206 			if (tinymce.isIE6) {
5207 				try {
5208 					d.execCommand('BackgroundImageCache', false, true);
5209 				} catch (e) {
5210 					t.cssFlicker = true;
5211 				}
5212 			}
5213 
5214 			t.fixDoc(d);
5215 			t.events = s.ownEvents ? new tinymce.dom.EventUtils(s.proxy) : tinymce.dom.Event;
5216 			tinymce.addUnload(t.destroy, t);
5217 			blockElementsMap = s.schema ? s.schema.getBlockElements() : {};
5218 
5219 			t.isBlock = function(node) {
5220 				// This function is called in module pattern style since it might be executed with the wrong this scope
5221 				var type = node.nodeType;
5222 
5223 				// If it's a node then check the type and use the nodeName
5224 				if (type)
5225 					return !!(type === 1 && blockElementsMap[node.nodeName]);
5226 
5227 				return !!blockElementsMap[node];
5228 			};
5229 		},
5230 
5231 		fixDoc: function(doc) {
5232 			var settings = this.settings, name;
5233 
5234 			if (isIE && settings.schema) {
5235 				// Add missing HTML 4/5 elements to IE
5236 				('abbr article aside audio canvas ' +
5237 				'details figcaption figure footer ' +
5238 				'header hgroup mark menu meter nav ' +
5239 				'output progress section summary ' +
5240 				'time video').replace(/\w+/g, function(name) {
5241 					doc.createElement(name);
5242 				});
5243 
5244 				// Create all custom elements
5245 				for (name in settings.schema.getCustomElements()) {
5246 					doc.createElement(name);
5247 				}
5248 			}
5249 		},
5250 
5251 		clone: function(node, deep) {
5252 			var self = this, clone, doc;
5253 
5254 			// TODO: Add feature detection here in the future
5255 			if (!isIE || node.nodeType !== 1 || deep) {
5256 				return node.cloneNode(deep);
5257 			}
5258 
5259 			doc = self.doc;
5260 
5261 			// Make a HTML5 safe shallow copy
5262 			if (!deep) {
5263 				clone = doc.createElement(node.nodeName);
5264 
5265 				// Copy attribs
5266 				each(self.getAttribs(node), function(attr) {
5267 					self.setAttrib(clone, attr.nodeName, self.getAttrib(node, attr.nodeName));
5268 				});
5269 
5270 				return clone;
5271 			}
5272 /*
5273 			// Setup HTML5 patched document fragment
5274 			if (!self.frag) {
5275 				self.frag = doc.createDocumentFragment();
5276 				self.fixDoc(self.frag);
5277 			}
5278 
5279 			// Make a deep copy by adding it to the document fragment then removing it this removed the :section
5280 			clone = doc.createElement('div');
5281 			self.frag.appendChild(clone);
5282 			clone.innerHTML = node.outerHTML;
5283 			self.frag.removeChild(clone);
5284 */
5285 			return clone.firstChild;
5286 		},
5287 
5288 		getRoot : function() {
5289 			var t = this, s = t.settings;
5290 
5291 			return (s && t.get(s.root_element)) || t.doc.body;
5292 		},
5293 
5294 		getViewPort : function(w) {
5295 			var d, b;
5296 
5297 			w = !w ? this.win : w;
5298 			d = w.document;
5299 			b = this.boxModel ? d.documentElement : d.body;
5300 
5301 			// Returns viewport size excluding scrollbars
5302 			return {
5303 				x : w.pageXOffset || b.scrollLeft,
5304 				y : w.pageYOffset || b.scrollTop,
5305 				w : w.innerWidth || b.clientWidth,
5306 				h : w.innerHeight || b.clientHeight
5307 			};
5308 		},
5309 
5310 		getRect : function(e) {
5311 			var p, t = this, sr;
5312 
5313 			e = t.get(e);
5314 			p = t.getPos(e);
5315 			sr = t.getSize(e);
5316 
5317 			return {
5318 				x : p.x,
5319 				y : p.y,
5320 				w : sr.w,
5321 				h : sr.h
5322 			};
5323 		},
5324 
5325 		getSize : function(e) {
5326 			var t = this, w, h;
5327 
5328 			e = t.get(e);
5329 			w = t.getStyle(e, 'width');
5330 			h = t.getStyle(e, 'height');
5331 
5332 			// Non pixel value, then force offset/clientWidth
5333 			if (w.indexOf('px') === -1)
5334 				w = 0;
5335 
5336 			// Non pixel value, then force offset/clientWidth
5337 			if (h.indexOf('px') === -1)
5338 				h = 0;
5339 
5340 			return {
5341 				w : parseInt(w, 10) || e.offsetWidth || e.clientWidth,
5342 				h : parseInt(h, 10) || e.offsetHeight || e.clientHeight
5343 			};
5344 		},
5345 
5346 		getParent : function(n, f, r) {
5347 			return this.getParents(n, f, r, false);
5348 		},
5349 
5350 		getParents : function(n, f, r, c) {
5351 			var t = this, na, se = t.settings, o = [];
5352 
5353 			n = t.get(n);
5354 			c = c === undefined;
5355 
5356 			if (se.strict_root)
5357 				r = r || t.getRoot();
5358 
5359 			// Wrap node name as func
5360 			if (is(f, 'string')) {
5361 				na = f;
5362 
5363 				if (f === '*') {
5364 					f = function(n) {return n.nodeType == 1;};
5365 				} else {
5366 					f = function(n) {
5367 						return t.is(n, na);
5368 					};
5369 				}
5370 			}
5371 
5372 			while (n) {
5373 				if (n == r || !n.nodeType || n.nodeType === 9)
5374 					break;
5375 
5376 				if (!f || f(n)) {
5377 					if (c)
5378 						o.push(n);
5379 					else
5380 						return n;
5381 				}
5382 
5383 				n = n.parentNode;
5384 			}
5385 
5386 			return c ? o : null;
5387 		},
5388 
5389 		get : function(e) {
5390 			var n;
5391 
5392 			if (e && this.doc && typeof(e) == 'string') {
5393 				n = e;
5394 				e = this.doc.getElementById(e);
5395 
5396 				// IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick
5397 				if (e && e.id !== n)
5398 					return this.doc.getElementsByName(n)[1];
5399 			}
5400 
5401 			return e;
5402 		},
5403 
5404 		getNext : function(node, selector) {
5405 			return this._findSib(node, selector, 'nextSibling');
5406 		},
5407 
5408 		getPrev : function(node, selector) {
5409 			return this._findSib(node, selector, 'previousSibling');
5410 		},
5411 
5412 
5413 		add : function(p, n, a, h, c) {
5414 			var t = this;
5415 
5416 			return this.run(p, function(p) {
5417 				var e, k;
5418 
5419 				e = is(n, 'string') ? t.doc.createElement(n) : n;
5420 				t.setAttribs(e, a);
5421 
5422 				if (h) {
5423 					if (h.nodeType)
5424 						e.appendChild(h);
5425 					else
5426 						t.setHTML(e, h);
5427 				}
5428 
5429 				return !c ? p.appendChild(e) : e;
5430 			});
5431 		},
5432 
5433 		create : function(n, a, h) {
5434 			return this.add(this.doc.createElement(n), n, a, h, 1);
5435 		},
5436 
5437 		createHTML : function(n, a, h) {
5438 			var o = '', t = this, k;
5439 
5440 			o += '<' + n;
5441 
5442 			for (k in a) {
5443 				if (a.hasOwnProperty(k))
5444 					o += ' ' + k + '="' + t.encode(a[k]) + '"';
5445 			}
5446 
5447 			// A call to tinymce.is doesn't work for some odd reason on IE9 possible bug inside their JS runtime
5448 			if (typeof(h) != "undefined")
5449 				return o + '>' + h + '</' + n + '>';
5450 
5451 			return o + ' />';
5452 		},
5453 
5454 		remove : function(node, keep_children) {
5455 			return this.run(node, function(node) {
5456 				var child, parent = node.parentNode;
5457 
5458 				if (!parent)
5459 					return null;
5460 
5461 				if (keep_children) {
5462 					while (child = node.firstChild) {
5463 						// IE 8 will crash if you don't remove completely empty text nodes
5464 						if (!tinymce.isIE || child.nodeType !== 3 || child.nodeValue)
5465 							parent.insertBefore(child, node);
5466 						else
5467 							node.removeChild(child);
5468 					}
5469 				}
5470 
5471 				return parent.removeChild(node);
5472 			});
5473 		},
5474 
5475 		setStyle : function(n, na, v) {
5476 			var t = this;
5477 
5478 			return t.run(n, function(e) {
5479 				var s, i;
5480 
5481 				s = e.style;
5482 
5483 				// Camelcase it, if needed
5484 				na = na.replace(/-(\D)/g, function(a, b){
5485 					return b.toUpperCase();
5486 				});
5487 
5488 				// Default px suffix on these
5489 				if (t.pixelStyles.test(na) && (tinymce.is(v, 'number') || /^[\-0-9\.]+$/.test(v)))
5490 					v += 'px';
5491 
5492 				switch (na) {
5493 					case 'opacity':
5494 						// IE specific opacity
5495 						if (isIE) {
5496 							s.filter = v === '' ? '' : "alpha(opacity=" + (v * 100) + ")";
5497 
5498 							if (!n.currentStyle || !n.currentStyle.hasLayout)
5499 								s.display = 'inline-block';
5500 						}
5501 
5502 						// Fix for older browsers
5503 						s[na] = s['-moz-opacity'] = s['-khtml-opacity'] = v || '';
5504 						break;
5505 
5506 					case 'float':
5507 						isIE ? s.styleFloat = v : s.cssFloat = v;
5508 						break;
5509 					
5510 					default:
5511 						s[na] = v || '';
5512 				}
5513 
5514 				// Force update of the style data
5515 				if (t.settings.update_styles)
5516 					t.setAttrib(e, 'data-mce-style');
5517 			});
5518 		},
5519 
5520 		getStyle : function(n, na, c) {
5521 			n = this.get(n);
5522 
5523 			if (!n)
5524 				return;
5525 
5526 			// Gecko
5527 			if (this.doc.defaultView && c) {
5528 				// Remove camelcase
5529 				na = na.replace(/[A-Z]/g, function(a){
5530 					return '-' + a;
5531 				});
5532 
5533 				try {
5534 					return this.doc.defaultView.getComputedStyle(n, null).getPropertyValue(na);
5535 				} catch (ex) {
5536 					// Old safari might fail
5537 					return null;
5538 				}
5539 			}
5540 
5541 			// Camelcase it, if needed
5542 			na = na.replace(/-(\D)/g, function(a, b){
5543 				return b.toUpperCase();
5544 			});
5545 
5546 			if (na == 'float')
5547 				na = isIE ? 'styleFloat' : 'cssFloat';
5548 
5549 			// IE & Opera
5550 			if (n.currentStyle && c)
5551 				return n.currentStyle[na];
5552 
5553 			return n.style ? n.style[na] : undefined;
5554 		},
5555 
5556 		setStyles : function(e, o) {
5557 			var t = this, s = t.settings, ol;
5558 
5559 			ol = s.update_styles;
5560 			s.update_styles = 0;
5561 
5562 			each(o, function(v, n) {
5563 				t.setStyle(e, n, v);
5564 			});
5565 
5566 			// Update style info
5567 			s.update_styles = ol;
5568 			if (s.update_styles)
5569 				t.setAttrib(e, s.cssText);
5570 		},
5571 
5572 		removeAllAttribs: function(e) {
5573 			return this.run(e, function(e) {
5574 				var i, attrs = e.attributes;
5575 				for (i = attrs.length - 1; i >= 0; i--) {
5576 					e.removeAttributeNode(attrs.item(i));
5577 				}
5578 			});
5579 		},
5580 
5581 		setAttrib : function(e, n, v) {
5582 			var t = this;
5583 
5584 			// Whats the point
5585 			if (!e || !n)
5586 				return;
5587 
5588 			// Strict XML mode
5589 			if (t.settings.strict)
5590 				n = n.toLowerCase();
5591 
5592 			return this.run(e, function(e) {
5593 				var s = t.settings;
5594 				var originalValue = e.getAttribute(n);
5595 				if (v !== null) {
5596 					switch (n) {
5597 						case "style":
5598 							if (!is(v, 'string')) {
5599 								each(v, function(v, n) {
5600 									t.setStyle(e, n, v);
5601 								});
5602 
5603 								return;
5604 							}
5605 
5606 							// No mce_style for elements with these since they might get resized by the user
5607 							if (s.keep_values) {
5608 								if (v && !t._isRes(v))
5609 									e.setAttribute('data-mce-style', v, 2);
5610 								else
5611 									e.removeAttribute('data-mce-style', 2);
5612 							}
5613 
5614 							e.style.cssText = v;
5615 							break;
5616 
5617 						case "class":
5618 							e.className = v || ''; // Fix IE null bug
5619 							break;
5620 
5621 						case "src":
5622 						case "href":
5623 							if (s.keep_values) {
5624 								if (s.url_converter)
5625 									v = s.url_converter.call(s.url_converter_scope || t, v, n, e);
5626 
5627 								t.setAttrib(e, 'data-mce-' + n, v, 2);
5628 							}
5629 
5630 							break;
5631 
5632 						case "shape":
5633 							e.setAttribute('data-mce-style', v);
5634 							break;
5635 					}
5636 				}
5637 				if (is(v) && v !== null && v.length !== 0)
5638 					e.setAttribute(n, '' + v, 2);
5639 				else
5640 					e.removeAttribute(n, 2);
5641 
5642 				// fire onChangeAttrib event for attributes that have changed
5643 				if (tinyMCE.activeEditor && originalValue != v) {
5644 					var ed = tinyMCE.activeEditor;
5645 					ed.onSetAttrib.dispatch(ed, e, n, v);
5646 				}
5647 			});
5648 		},
5649 
5650 		setAttribs : function(e, o) {
5651 			var t = this;
5652 
5653 			return this.run(e, function(e) {
5654 				each(o, function(v, n) {
5655 					t.setAttrib(e, n, v);
5656 				});
5657 			});
5658 		},
5659 
5660 		getAttrib : function(e, n, dv) {
5661 			var v, t = this, undef;
5662 
5663 			e = t.get(e);
5664 
5665 			if (!e || e.nodeType !== 1)
5666 				return dv === undef ? false : dv;
5667 
5668 			if (!is(dv))
5669 				dv = '';
5670 
5671 			// Try the mce variant for these
5672 			if (/^(src|href|style|coords|shape)$/.test(n)) {
5673 				v = e.getAttribute("data-mce-" + n);
5674 
5675 				if (v)
5676 					return v;
5677 			}
5678 
5679 			if (isIE && t.props[n]) {
5680 				v = e[t.props[n]];
5681 				v = v && v.nodeValue ? v.nodeValue : v;
5682 			}
5683 
5684 			if (!v)
5685 				v = e.getAttribute(n, 2);
5686 
5687 			// Check boolean attribs
5688 			if (/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(n)) {
5689 				if (e[t.props[n]] === true && v === '')
5690 					return n;
5691 
5692 				return v ? n : '';
5693 			}
5694 
5695 			// Inner input elements will override attributes on form elements
5696 			if (e.nodeName === "FORM" && e.getAttributeNode(n))
5697 				return e.getAttributeNode(n).nodeValue;
5698 
5699 			if (n === 'style') {
5700 				v = v || e.style.cssText;
5701 
5702 				if (v) {
5703 					v = t.serializeStyle(t.parseStyle(v), e.nodeName);
5704 
5705 					if (t.settings.keep_values && !t._isRes(v))
5706 						e.setAttribute('data-mce-style', v);
5707 				}
5708 			}
5709 
5710 			// Remove Apple and WebKit stuff
5711 			if (isWebKit && n === "class" && v)
5712 				v = v.replace(/(apple|webkit)\-[a-z\-]+/gi, '');
5713 
5714 			// Handle IE issues
5715 			if (isIE) {
5716 				switch (n) {
5717 					case 'rowspan':
5718 					case 'colspan':
5719 						// IE returns 1 as default value
5720 						if (v === 1)
5721 							v = '';
5722 
5723 						break;
5724 
5725 					case 'size':
5726 						// IE returns +0 as default value for size
5727 						if (v === '+0' || v === 20 || v === 0)
5728 							v = '';
5729 
5730 						break;
5731 
5732 					case 'width':
5733 					case 'height':
5734 					case 'vspace':
5735 					case 'checked':
5736 					case 'disabled':
5737 					case 'readonly':
5738 						if (v === 0)
5739 							v = '';
5740 
5741 						break;
5742 
5743 					case 'hspace':
5744 						// IE returns -1 as default value
5745 						if (v === -1)
5746 							v = '';
5747 
5748 						break;
5749 
5750 					case 'maxlength':
5751 					case 'tabindex':
5752 						// IE returns default value
5753 						if (v === 32768 || v === 2147483647 || v === '32768')
5754 							v = '';
5755 
5756 						break;
5757 
5758 					case 'multiple':
5759 					case 'compact':
5760 					case 'noshade':
5761 					case 'nowrap':
5762 						if (v === 65535)
5763 							return n;
5764 
5765 						return dv;
5766 
5767 					case 'shape':
5768 						v = v.toLowerCase();
5769 						break;
5770 
5771 					default:
5772 						// IE has odd anonymous function for event attributes
5773 						if (n.indexOf('on') === 0 && v)
5774 							v = tinymce._replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/, '$1', '' + v);
5775 				}
5776 			}
5777 
5778 			return (v !== undef && v !== null && v !== '') ? '' + v : dv;
5779 		},
5780 
5781 		getPos : function(n, ro) {
5782 			var t = this, x = 0, y = 0, e, d = t.doc, r;
5783 
5784 			n = t.get(n);
5785 			ro = ro || d.body;
5786 
5787 			if (n) {
5788 				// Use getBoundingClientRect if it exists since it's faster than looping offset nodes
5789 				if (n.getBoundingClientRect) {
5790 					n = n.getBoundingClientRect();
5791 					e = t.boxModel ? d.documentElement : d.body;
5792 
5793 					// Add scroll offsets from documentElement or body since IE with the wrong box model will use d.body and so do WebKit
5794 					// Also remove the body/documentelement clientTop/clientLeft on IE 6, 7 since they offset the position
5795 					x = n.left + (d.documentElement.scrollLeft || d.body.scrollLeft) - e.clientTop;
5796 					y = n.top + (d.documentElement.scrollTop || d.body.scrollTop) - e.clientLeft;
5797 
5798 					return {x : x, y : y};
5799 				}
5800 
5801 				r = n;
5802 				while (r && r != ro && r.nodeType) {
5803 					x += r.offsetLeft || 0;
5804 					y += r.offsetTop || 0;
5805 					r = r.offsetParent;
5806 				}
5807 
5808 				r = n.parentNode;
5809 				while (r && r != ro && r.nodeType) {
5810 					x -= r.scrollLeft || 0;
5811 					y -= r.scrollTop || 0;
5812 					r = r.parentNode;
5813 				}
5814 			}
5815 
5816 			return {x : x, y : y};
5817 		},
5818 
5819 		parseStyle : function(st) {
5820 			return this.styles.parse(st);
5821 		},
5822 
5823 		serializeStyle : function(o, name) {
5824 			return this.styles.serialize(o, name);
5825 		},
5826 
5827 		addStyle: function(cssText) {
5828 			var doc = this.doc, head;
5829 
5830 			// Create style element if needed
5831 			styleElm = doc.getElementById('mceDefaultStyles');
5832 			if (!styleElm) {
5833 				styleElm = doc.createElement('style'),
5834 				styleElm.id = 'mceDefaultStyles';
5835 				styleElm.type = 'text/css';
5836 
5837 				head = doc.getElementsByTagName('head')[0]
5838 				if (head.firstChild) {
5839 					head.insertBefore(styleElm, head.firstChild);
5840 				} else {
5841 					head.appendChild(styleElm);
5842 				}
5843 			}
5844 
5845 			// Append style data to old or new style element
5846 			if (styleElm.styleSheet) {
5847 				styleElm.styleSheet.cssText += cssText;
5848 			} else {
5849 				styleElm.appendChild(doc.createTextNode(cssText));
5850 			}
5851 		},
5852 
5853 		loadCSS : function(u) {
5854 			var t = this, d = t.doc, head;
5855 
5856 			if (!u)
5857 				u = '';
5858 
5859 			head = d.getElementsByTagName('head')[0];
5860 
5861 			each(u.split(','), function(u) {
5862 				var link;
5863 
5864 				if (t.files[u])
5865 					return;
5866 
5867 				t.files[u] = true;
5868 				link = t.create('link', {rel : 'stylesheet', href : tinymce._addVer(u)});
5869 
5870 				// IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug
5871 				// This fix seems to resolve that issue by realcing the document ones a stylesheet finishes loading
5872 				// It's ugly but it seems to work fine.
5873 				if (isIE && d.documentMode && d.recalc) {
5874 					link.onload = function() {
5875 						if (d.recalc)
5876 							d.recalc();
5877 
5878 						link.onload = null;
5879 					};
5880 				}
5881 
5882 				head.appendChild(link);
5883 			});
5884 		},
5885 
5886 		addClass : function(e, c) {
5887 			return this.run(e, function(e) {
5888 				var o;
5889 
5890 				if (!c)
5891 					return 0;
5892 
5893 				if (this.hasClass(e, c))
5894 					return e.className;
5895 
5896 				o = this.removeClass(e, c);
5897 
5898 				return e.className = (o != '' ? (o + ' ') : '') + c;
5899 			});
5900 		},
5901 
5902 		removeClass : function(e, c) {
5903 			var t = this, re;
5904 
5905 			return t.run(e, function(e) {
5906 				var v;
5907 
5908 				if (t.hasClass(e, c)) {
5909 					if (!re)
5910 						re = new RegExp("(^|\\s+)" + c + "(\\s+|$)", "g");
5911 
5912 					v = e.className.replace(re, ' ');
5913 					v = tinymce.trim(v != ' ' ? v : '');
5914 
5915 					e.className = v;
5916 
5917 					// Empty class attr
5918 					if (!v) {
5919 						e.removeAttribute('class');
5920 						e.removeAttribute('className');
5921 					}
5922 
5923 					return v;
5924 				}
5925 
5926 				return e.className;
5927 			});
5928 		},
5929 
5930 		hasClass : function(n, c) {
5931 			n = this.get(n);
5932 
5933 			if (!n || !c)
5934 				return false;
5935 
5936 			return (' ' + n.className + ' ').indexOf(' ' + c + ' ') !== -1;
5937 		},
5938 
5939 		show : function(e) {
5940 			return this.setStyle(e, 'display', 'block');
5941 		},
5942 
5943 		hide : function(e) {
5944 			return this.setStyle(e, 'display', 'none');
5945 		},
5946 
5947 		isHidden : function(e) {
5948 			e = this.get(e);
5949 
5950 			return !e || e.style.display == 'none' || this.getStyle(e, 'display') == 'none';
5951 		},
5952 
5953 		uniqueId : function(p) {
5954 			return (!p ? 'mce_' : p) + (this.counter++);
5955 		},
5956 
5957 		setHTML : function(element, html) {
5958 			var self = this;
5959 
5960 			return self.run(element, function(element) {
5961 				if (isIE) {
5962 					// Remove all child nodes, IE keeps empty text nodes in DOM
5963 					while (element.firstChild)
5964 						element.removeChild(element.firstChild);
5965 
5966 					try {
5967 						// IE will remove comments from the beginning
5968 						// unless you padd the contents with something
5969 						element.innerHTML = '<br />' + html;
5970 						element.removeChild(element.firstChild);
5971 					} catch (ex) {
5972 						// IE sometimes produces an unknown runtime error on innerHTML if it's an block element within a block element for example a div inside a p
5973 						// This seems to fix this problem
5974 
5975 						// Create new div with HTML contents and a BR infront to keep comments
5976 						var newElement = self.create('div');
5977 						newElement.innerHTML = '<br />' + html;
5978 
5979 						// Add all children from div to target
5980 						each (tinymce.grep(newElement.childNodes), function(node, i) {
5981 							// Skip br element
5982 							if (i && element.canHaveHTML)
5983 								element.appendChild(node);
5984 						});
5985 					}
5986 				} else
5987 					element.innerHTML = html;
5988 
5989 				return html;
5990 			});
5991 		},
5992 
5993 		getOuterHTML : function(elm) {
5994 			var doc, self = this;
5995 
5996 			elm = self.get(elm);
5997 
5998 			if (!elm)
5999 				return null;
6000 
6001 			if (elm.nodeType === 1 && self.hasOuterHTML)
6002 				return elm.outerHTML;
6003 
6004 			doc = (elm.ownerDocument || self.doc).createElement("body");
6005 			doc.appendChild(elm.cloneNode(true));
6006 
6007 			return doc.innerHTML;
6008 		},
6009 
6010 		setOuterHTML : function(e, h, d) {
6011 			var t = this;
6012 
6013 			function setHTML(e, h, d) {
6014 				var n, tp;
6015 
6016 				tp = d.createElement("body");
6017 				tp.innerHTML = h;
6018 
6019 				n = tp.lastChild;
6020 				while (n) {
6021 					t.insertAfter(n.cloneNode(true), e);
6022 					n = n.previousSibling;
6023 				}
6024 
6025 				t.remove(e);
6026 			};
6027 
6028 			return this.run(e, function(e) {
6029 				e = t.get(e);
6030 
6031 				// Only set HTML on elements
6032 				if (e.nodeType == 1) {
6033 					d = d || e.ownerDocument || t.doc;
6034 
6035 					if (isIE) {
6036 						try {
6037 							// Try outerHTML for IE it sometimes produces an unknown runtime error
6038 							if (isIE && e.nodeType == 1)
6039 								e.outerHTML = h;
6040 							else
6041 								setHTML(e, h, d);
6042 						} catch (ex) {
6043 							// Fix for unknown runtime error
6044 							setHTML(e, h, d);
6045 						}
6046 					} else
6047 						setHTML(e, h, d);
6048 				}
6049 			});
6050 		},
6051 
6052 		decode : Entities.decode,
6053 
6054 		encode : Entities.encodeAllRaw,
6055 
6056 		insertAfter : function(node, reference_node) {
6057 			reference_node = this.get(reference_node);
6058 
6059 			return this.run(node, function(node) {
6060 				var parent, nextSibling;
6061 
6062 				parent = reference_node.parentNode;
6063 				nextSibling = reference_node.nextSibling;
6064 
6065 				if (nextSibling)
6066 					parent.insertBefore(node, nextSibling);
6067 				else
6068 					parent.appendChild(node);
6069 
6070 				return node;
6071 			});
6072 		},
6073 
6074 		replace : function(n, o, k) {
6075 			var t = this;
6076 
6077 			if (is(o, 'array'))
6078 				n = n.cloneNode(true);
6079 
6080 			return t.run(o, function(o) {
6081 				if (k) {
6082 					each(tinymce.grep(o.childNodes), function(c) {
6083 						n.appendChild(c);
6084 					});
6085 				}
6086 
6087 				return o.parentNode.replaceChild(n, o);
6088 			});
6089 		},
6090 
6091 		rename : function(elm, name) {
6092 			var t = this, newElm;
6093 
6094 			if (elm.nodeName != name.toUpperCase()) {
6095 				// Rename block element
6096 				newElm = t.create(name);
6097 
6098 				// Copy attribs to new block
6099 				each(t.getAttribs(elm), function(attr_node) {
6100 					t.setAttrib(newElm, attr_node.nodeName, t.getAttrib(elm, attr_node.nodeName));
6101 				});
6102 
6103 				// Replace block
6104 				t.replace(newElm, elm, 1);
6105 			}
6106 
6107 			return newElm || elm;
6108 		},
6109 
6110 		findCommonAncestor : function(a, b) {
6111 			var ps = a, pe;
6112 
6113 			while (ps) {
6114 				pe = b;
6115 
6116 				while (pe && ps != pe)
6117 					pe = pe.parentNode;
6118 
6119 				if (ps == pe)
6120 					break;
6121 
6122 				ps = ps.parentNode;
6123 			}
6124 
6125 			if (!ps && a.ownerDocument)
6126 				return a.ownerDocument.documentElement;
6127 
6128 			return ps;
6129 		},
6130 
6131 		toHex : function(s) {
6132 			var c = /^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(s);
6133 
6134 			function hex(s) {
6135 				s = parseInt(s, 10).toString(16);
6136 
6137 				return s.length > 1 ? s : '0' + s; // 0 -> 00
6138 			};
6139 
6140 			if (c) {
6141 				s = '#' + hex(c[1]) + hex(c[2]) + hex(c[3]);
6142 
6143 				return s;
6144 			}
6145 
6146 			return s;
6147 		},
6148 
6149 		getClasses : function() {
6150 			var t = this, cl = [], i, lo = {}, f = t.settings.class_filter, ov;
6151 
6152 			if (t.classes)
6153 				return t.classes;
6154 
6155 			function addClasses(s) {
6156 				// IE style imports
6157 				each(s.imports, function(r) {
6158 					addClasses(r);
6159 				});
6160 
6161 				each(s.cssRules || s.rules, function(r) {
6162 					// Real type or fake it on IE
6163 					switch (r.type || 1) {
6164 						// Rule
6165 						case 1:
6166 							if (r.selectorText) {
6167 								each(r.selectorText.split(','), function(v) {
6168 									v = v.replace(/^\s*|\s*$|^\s\./g, "");
6169 
6170 									// Is internal or it doesn't contain a class
6171 									if (/\.mce/.test(v) || !/\.[\w\-]+$/.test(v))
6172 										return;
6173 
6174 									// Remove everything but class name
6175 									ov = v;
6176 									v = tinymce._replace(/.*\.([a-z0-9_\-]+).*/i, '$1', v);
6177 
6178 									// Filter classes
6179 									if (f && !(v = f(v, ov)))
6180 										return;
6181 
6182 									if (!lo[v]) {
6183 										cl.push({'class' : v});
6184 										lo[v] = 1;
6185 									}
6186 								});
6187 							}
6188 							break;
6189 
6190 						// Import
6191 						case 3:
6192 							addClasses(r.styleSheet);
6193 							break;
6194 					}
6195 				});
6196 			};
6197 
6198 			try {
6199 				each(t.doc.styleSheets, addClasses);
6200 			} catch (ex) {
6201 				// Ignore
6202 			}
6203 
6204 			if (cl.length > 0)
6205 				t.classes = cl;
6206 
6207 			return cl;
6208 		},
6209 
6210 		run : function(e, f, s) {
6211 			var t = this, o;
6212 
6213 			if (t.doc && typeof(e) === 'string')
6214 				e = t.get(e);
6215 
6216 			if (!e)
6217 				return false;
6218 
6219 			s = s || this;
6220 			if (!e.nodeType && (e.length || e.length === 0)) {
6221 				o = [];
6222 
6223 				each(e, function(e, i) {
6224 					if (e) {
6225 						if (typeof(e) == 'string')
6226 							e = t.doc.getElementById(e);
6227 
6228 						o.push(f.call(s, e, i));
6229 					}
6230 				});
6231 
6232 				return o;
6233 			}
6234 
6235 			return f.call(s, e);
6236 		},
6237 
6238 		getAttribs : function(n) {
6239 			var o;
6240 
6241 			n = this.get(n);
6242 
6243 			if (!n)
6244 				return [];
6245 
6246 			if (isIE) {
6247 				o = [];
6248 
6249 				// Object will throw exception in IE
6250 				if (n.nodeName == 'OBJECT')
6251 					return n.attributes;
6252 
6253 				// IE doesn't keep the selected attribute if you clone option elements
6254 				if (n.nodeName === 'OPTION' && this.getAttrib(n, 'selected'))
6255 					o.push({specified : 1, nodeName : 'selected'});
6256 
6257 				// It's crazy that this is faster in IE but it's because it returns all attributes all the time
6258 				n.cloneNode(false).outerHTML.replace(/<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi, '').replace(/[\w:\-]+/gi, function(a) {
6259 					o.push({specified : 1, nodeName : a});
6260 				});
6261 
6262 				return o;
6263 			}
6264 
6265 			return n.attributes;
6266 		},
6267 
6268 		isEmpty : function(node, elements) {
6269 			var self = this, i, attributes, type, walker, name, brCount = 0;
6270 
6271 			node = node.firstChild;
6272 			if (node) {
6273 				walker = new tinymce.dom.TreeWalker(node, node.parentNode);
6274 				elements = elements || self.schema ? self.schema.getNonEmptyElements() : null;
6275 
6276 				do {
6277 					type = node.nodeType;
6278 
6279 					if (type === 1) {
6280 						// Ignore bogus elements
6281 						if (node.getAttribute('data-mce-bogus'))
6282 							continue;
6283 
6284 						// Keep empty elements like <img />
6285 						name = node.nodeName.toLowerCase();
6286 						if (elements && elements[name]) {
6287 							// Ignore single BR elements in blocks like <p><br /></p> or <p><span><br /></span></p>
6288 							if (name === 'br') {
6289 								brCount++;
6290 								continue;
6291 							}
6292 
6293 							return false;
6294 						}
6295 
6296 						// Keep elements with data-bookmark attributes or name attribute like <a name="1"></a>
6297 						attributes = self.getAttribs(node);
6298 						i = node.attributes.length;
6299 						while (i--) {
6300 							name = node.attributes[i].nodeName;
6301 							if (name === "name" || name === 'data-mce-bookmark')
6302 								return false;
6303 						}
6304 					}
6305 
6306 					// Keep comment nodes
6307 					if (type == 8)
6308 						return false;
6309 
6310 					// Keep non whitespace text nodes
6311 					if ((type === 3 && !whiteSpaceRegExp.test(node.nodeValue)))
6312 						return false;
6313 				} while (node = walker.next());
6314 			}
6315 
6316 			return brCount <= 1;
6317 		},
6318 
6319 		destroy : function(s) {
6320 			var t = this;
6321 
6322 			t.win = t.doc = t.root = t.events = t.frag = null;
6323 
6324 			// Manual destroy then remove unload handler
6325 			if (!s)
6326 				tinymce.removeUnload(t.destroy);
6327 		},
6328 
6329 		createRng : function() {
6330 			var d = this.doc;
6331 
6332 			return d.createRange ? d.createRange() : new tinymce.dom.Range(this);
6333 		},
6334 
6335 		nodeIndex : function(node, normalized) {
6336 			var idx = 0, lastNodeType, lastNode, nodeType;
6337 
6338 			if (node) {
6339 				for (lastNodeType = node.nodeType, node = node.previousSibling, lastNode = node; node; node = node.previousSibling) {
6340 					nodeType = node.nodeType;
6341 
6342 					// Normalize text nodes
6343 					if (normalized && nodeType == 3) {
6344 						if (nodeType == lastNodeType || !node.nodeValue.length)
6345 							continue;
6346 					}
6347 					idx++;
6348 					lastNodeType = nodeType;
6349 				}
6350 			}
6351 
6352 			return idx;
6353 		},
6354 
6355 		split : function(pe, e, re) {
6356 			var t = this, r = t.createRng(), bef, aft, pa;
6357 
6358 			// W3C valid browsers tend to leave empty nodes to the left/right side of the contents, this makes sense
6359 			// but we don't want that in our code since it serves no purpose for the end user
6360 			// For example if this is chopped:
6361 			//   <p>text 1<span><b>CHOP</b></span>text 2</p>
6362 			// would produce:
6363 			//   <p>text 1<span></span></p><b>CHOP</b><p><span></span>text 2</p>
6364 			// this function will then trim of empty edges and produce:
6365 			//   <p>text 1</p><b>CHOP</b><p>text 2</p>
6366 			function trim(node) {
6367 				var i, children = node.childNodes, type = node.nodeType;
6368 
6369 				function surroundedBySpans(node) {
6370 					var previousIsSpan = node.previousSibling && node.previousSibling.nodeName == 'SPAN';
6371 					var nextIsSpan = node.nextSibling && node.nextSibling.nodeName == 'SPAN';
6372 					return previousIsSpan && nextIsSpan;
6373 				}
6374 
6375 				if (type == 1 && node.getAttribute('data-mce-type') == 'bookmark')
6376 					return;
6377 
6378 				for (i = children.length - 1; i >= 0; i--)
6379 					trim(children[i]);
6380 
6381 				if (type != 9) {
6382 					// Keep non whitespace text nodes
6383 					if (type == 3 && node.nodeValue.length > 0) {
6384 						// If parent element isn't a block or there isn't any useful contents for example "<p>   </p>"
6385 						// Also keep text nodes with only spaces if surrounded by spans.
6386 						// eg. "<p><span>a</span> <span>b</span></p>" should keep space between a and b
6387 						var trimmedLength = tinymce.trim(node.nodeValue).length;
6388 						if (!t.isBlock(node.parentNode) || trimmedLength > 0 || trimmedLength === 0 && surroundedBySpans(node))
6389 							return;
6390 					} else if (type == 1) {
6391 						// If the only child is a bookmark then move it up
6392 						children = node.childNodes;
6393 						if (children.length == 1 && children[0] && children[0].nodeType == 1 && children[0].getAttribute('data-mce-type') == 'bookmark')
6394 							node.parentNode.insertBefore(children[0], node);
6395 
6396 						// Keep non empty elements or img, hr etc
6397 						if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName))
6398 							return;
6399 					}
6400 
6401 					t.remove(node);
6402 				}
6403 
6404 				return node;
6405 			};
6406 
6407 			if (pe && e) {
6408 				// Get before chunk
6409 				r.setStart(pe.parentNode, t.nodeIndex(pe));
6410 				r.setEnd(e.parentNode, t.nodeIndex(e));
6411 				bef = r.extractContents();
6412 
6413 				// Get after chunk
6414 				r = t.createRng();
6415 				r.setStart(e.parentNode, t.nodeIndex(e) + 1);
6416 				r.setEnd(pe.parentNode, t.nodeIndex(pe) + 1);
6417 				aft = r.extractContents();
6418 
6419 				// Insert before chunk
6420 				pa = pe.parentNode;
6421 				pa.insertBefore(trim(bef), pe);
6422 
6423 				// Insert middle chunk
6424 				if (re)
6425 				pa.replaceChild(re, e);
6426 			else
6427 				pa.insertBefore(e, pe);
6428 
6429 				// Insert after chunk
6430 				pa.insertBefore(trim(aft), pe);
6431 				t.remove(pe);
6432 
6433 				return re || e;
6434 			}
6435 		},
6436 
6437 		bind : function(target, name, func, scope) {
6438 			return this.events.add(target, name, func, scope || this);
6439 		},
6440 
6441 		unbind : function(target, name, func) {
6442 			return this.events.remove(target, name, func);
6443 		},
6444 
6445 		fire : function(target, name, evt) {
6446 			return this.events.fire(target, name, evt);
6447 		},
6448 
6449 		// Returns the content editable state of a node
6450 		getContentEditable: function(node) {
6451 			var contentEditable;
6452 
6453 			// Check type
6454 			if (node.nodeType != 1) {
6455 				return null;
6456 			}
6457 
6458 			// Check for fake content editable
6459 			contentEditable = node.getAttribute("data-mce-contenteditable");
6460 			if (contentEditable && contentEditable !== "inherit") {
6461 				return contentEditable;
6462 			}
6463 
6464 			// Check for real content editable
6465 			return node.contentEditable !== "inherit" ? node.contentEditable : null;
6466 		},
6467 
6468 
6469 		_findSib : function(node, selector, name) {
6470 			var t = this, f = selector;
6471 
6472 			if (node) {
6473 				// If expression make a function of it using is
6474 				if (is(f, 'string')) {
6475 					f = function(node) {
6476 						return t.is(node, selector);
6477 					};
6478 				}
6479 
6480 				// Loop all siblings
6481 				for (node = node[name]; node; node = node[name]) {
6482 					if (f(node))
6483 						return node;
6484 				}
6485 			}
6486 
6487 			return null;
6488 		},
6489 
6490 		_isRes : function(c) {
6491 			// Is live resizble element
6492 			return /^(top|left|bottom|right|width|height)/i.test(c) || /;\s*(top|left|bottom|right|width|height)/i.test(c);
6493 		}
6494 
6495 		/*
6496 		walk : function(n, f, s) {
6497 			var d = this.doc, w;
6498 
6499 			if (d.createTreeWalker) {
6500 				w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false);
6501 
6502 				while ((n = w.nextNode()) != null)
6503 					f.call(s || this, n);
6504 			} else
6505 				tinymce.walk(n, f, 'childNodes', s);
6506 		}
6507 		*/
6508 
6509 		/*
6510 		toRGB : function(s) {
6511 			var c = /^\s*?#([0-9A-F]{2})([0-9A-F]{1,2})([0-9A-F]{2})?\s*?$/.exec(s);
6512 
6513 			if (c) {
6514 				// #FFF -> #FFFFFF
6515 				if (!is(c[3]))
6516 					c[3] = c[2] = c[1];
6517 
6518 				return "rgb(" + parseInt(c[1], 16) + "," + parseInt(c[2], 16) + "," + parseInt(c[3], 16) + ")";
6519 			}
6520 
6521 			return s;
6522 		}
6523 		*/
6524 	});
6525 
6526 	tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0});
6527 })(tinymce);
6528 
6529 (function(ns) {
6530 	// Range constructor
6531 	function Range(dom) {
6532 		var t = this,
6533 			doc = dom.doc,
6534 			EXTRACT = 0,
6535 			CLONE = 1,
6536 			DELETE = 2,
6537 			TRUE = true,
6538 			FALSE = false,
6539 			START_OFFSET = 'startOffset',
6540 			START_CONTAINER = 'startContainer',
6541 			END_CONTAINER = 'endContainer',
6542 			END_OFFSET = 'endOffset',
6543 			extend = tinymce.extend,
6544 			nodeIndex = dom.nodeIndex;
6545 
6546 		extend(t, {
6547 			// Inital states
6548 			startContainer : doc,
6549 			startOffset : 0,
6550 			endContainer : doc,
6551 			endOffset : 0,
6552 			collapsed : TRUE,
6553 			commonAncestorContainer : doc,
6554 
6555 			// Range constants
6556 			START_TO_START : 0,
6557 			START_TO_END : 1,
6558 			END_TO_END : 2,
6559 			END_TO_START : 3,
6560 
6561 			// Public methods
6562 			setStart : setStart,
6563 			setEnd : setEnd,
6564 			setStartBefore : setStartBefore,
6565 			setStartAfter : setStartAfter,
6566 			setEndBefore : setEndBefore,
6567 			setEndAfter : setEndAfter,
6568 			collapse : collapse,
6569 			selectNode : selectNode,
6570 			selectNodeContents : selectNodeContents,
6571 			compareBoundaryPoints : compareBoundaryPoints,
6572 			deleteContents : deleteContents,
6573 			extractContents : extractContents,
6574 			cloneContents : cloneContents,
6575 			insertNode : insertNode,
6576 			surroundContents : surroundContents,
6577 			cloneRange : cloneRange,
6578 			toStringIE : toStringIE
6579 		});
6580 
6581 		function createDocumentFragment() {
6582 			return doc.createDocumentFragment();
6583 		};
6584 
6585 		function setStart(n, o) {
6586 			_setEndPoint(TRUE, n, o);
6587 		};
6588 
6589 		function setEnd(n, o) {
6590 			_setEndPoint(FALSE, n, o);
6591 		};
6592 
6593 		function setStartBefore(n) {
6594 			setStart(n.parentNode, nodeIndex(n));
6595 		};
6596 
6597 		function setStartAfter(n) {
6598 			setStart(n.parentNode, nodeIndex(n) + 1);
6599 		};
6600 
6601 		function setEndBefore(n) {
6602 			setEnd(n.parentNode, nodeIndex(n));
6603 		};
6604 
6605 		function setEndAfter(n) {
6606 			setEnd(n.parentNode, nodeIndex(n) + 1);
6607 		};
6608 
6609 		function collapse(ts) {
6610 			if (ts) {
6611 				t[END_CONTAINER] = t[START_CONTAINER];
6612 				t[END_OFFSET] = t[START_OFFSET];
6613 			} else {
6614 				t[START_CONTAINER] = t[END_CONTAINER];
6615 				t[START_OFFSET] = t[END_OFFSET];
6616 			}
6617 
6618 			t.collapsed = TRUE;
6619 		};
6620 
6621 		function selectNode(n) {
6622 			setStartBefore(n);
6623 			setEndAfter(n);
6624 		};
6625 
6626 		function selectNodeContents(n) {
6627 			setStart(n, 0);
6628 			setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length);
6629 		};
6630 
6631 		function compareBoundaryPoints(h, r) {
6632 			var sc = t[START_CONTAINER], so = t[START_OFFSET], ec = t[END_CONTAINER], eo = t[END_OFFSET],
6633 			rsc = r.startContainer, rso = r.startOffset, rec = r.endContainer, reo = r.endOffset;
6634 
6635 			// Check START_TO_START
6636 			if (h === 0)
6637 				return _compareBoundaryPoints(sc, so, rsc, rso);
6638 	
6639 			// Check START_TO_END
6640 			if (h === 1)
6641 				return _compareBoundaryPoints(ec, eo, rsc, rso);
6642 	
6643 			// Check END_TO_END
6644 			if (h === 2)
6645 				return _compareBoundaryPoints(ec, eo, rec, reo);
6646 	
6647 			// Check END_TO_START
6648 			if (h === 3) 
6649 				return _compareBoundaryPoints(sc, so, rec, reo);
6650 		};
6651 
6652 		function deleteContents() {
6653 			_traverse(DELETE);
6654 		};
6655 
6656 		function extractContents() {
6657 			return _traverse(EXTRACT);
6658 		};
6659 
6660 		function cloneContents() {
6661 			return _traverse(CLONE);
6662 		};
6663 
6664 		function insertNode(n) {
6665 			var startContainer = this[START_CONTAINER],
6666 				startOffset = this[START_OFFSET], nn, o;
6667 
6668 			// Node is TEXT_NODE or CDATA
6669 			if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) {
6670 				if (!startOffset) {
6671 					// At the start of text
6672 					startContainer.parentNode.insertBefore(n, startContainer);
6673 				} else if (startOffset >= startContainer.nodeValue.length) {
6674 					// At the end of text
6675 					dom.insertAfter(n, startContainer);
6676 				} else {
6677 					// Middle, need to split
6678 					nn = startContainer.splitText(startOffset);
6679 					startContainer.parentNode.insertBefore(n, nn);
6680 				}
6681 			} else {
6682 				// Insert element node
6683 				if (startContainer.childNodes.length > 0)
6684 					o = startContainer.childNodes[startOffset];
6685 
6686 				if (o)
6687 					startContainer.insertBefore(n, o);
6688 				else
6689 					startContainer.appendChild(n);
6690 			}
6691 		};
6692 
6693 		function surroundContents(n) {
6694 			var f = t.extractContents();
6695 
6696 			t.insertNode(n);
6697 			n.appendChild(f);
6698 			t.selectNode(n);
6699 		};
6700 
6701 		function cloneRange() {
6702 			return extend(new Range(dom), {
6703 				startContainer : t[START_CONTAINER],
6704 				startOffset : t[START_OFFSET],
6705 				endContainer : t[END_CONTAINER],
6706 				endOffset : t[END_OFFSET],
6707 				collapsed : t.collapsed,
6708 				commonAncestorContainer : t.commonAncestorContainer
6709 			});
6710 		};
6711 
6712 		// Private methods
6713 
6714 		function _getSelectedNode(container, offset) {
6715 			var child;
6716 
6717 			if (container.nodeType == 3 /* TEXT_NODE */)
6718 				return container;
6719 
6720 			if (offset < 0)
6721 				return container;
6722 
6723 			child = container.firstChild;
6724 			while (child && offset > 0) {
6725 				--offset;
6726 				child = child.nextSibling;
6727 			}
6728 
6729 			if (child)
6730 				return child;
6731 
6732 			return container;
6733 		};
6734 
6735 		function _isCollapsed() {
6736 			return (t[START_CONTAINER] == t[END_CONTAINER] && t[START_OFFSET] == t[END_OFFSET]);
6737 		};
6738 
6739 		function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) {
6740 			var c, offsetC, n, cmnRoot, childA, childB;
6741 			
6742 			// In the first case the boundary-points have the same container. A is before B
6743 			// if its offset is less than the offset of B, A is equal to B if its offset is
6744 			// equal to the offset of B, and A is after B if its offset is greater than the
6745 			// offset of B.
6746 			if (containerA == containerB) {
6747 				if (offsetA == offsetB)
6748 					return 0; // equal
6749 
6750 				if (offsetA < offsetB)
6751 					return -1; // before
6752 
6753 				return 1; // after
6754 			}
6755 
6756 			// In the second case a child node C of the container of A is an ancestor
6757 			// container of B. In this case, A is before B if the offset of A is less than or
6758 			// equal to the index of the child node C and A is after B otherwise.
6759 			c = containerB;
6760 			while (c && c.parentNode != containerA)
6761 				c = c.parentNode;
6762 
6763 			if (c) {
6764 				offsetC = 0;
6765 				n = containerA.firstChild;
6766 
6767 				while (n != c && offsetC < offsetA) {
6768 					offsetC++;
6769 					n = n.nextSibling;
6770 				}
6771 
6772 				if (offsetA <= offsetC)
6773 					return -1; // before
6774 
6775 				return 1; // after
6776 			}
6777 
6778 			// In the third case a child node C of the container of B is an ancestor container
6779 			// of A. In this case, A is before B if the index of the child node C is less than
6780 			// the offset of B and A is after B otherwise.
6781 			c = containerA;
6782 			while (c && c.parentNode != containerB) {
6783 				c = c.parentNode;
6784 			}
6785 
6786 			if (c) {
6787 				offsetC = 0;
6788 				n = containerB.firstChild;
6789 
6790 				while (n != c && offsetC < offsetB) {
6791 					offsetC++;
6792 					n = n.nextSibling;
6793 				}
6794 
6795 				if (offsetC < offsetB)
6796 					return -1; // before
6797 
6798 				return 1; // after
6799 			}
6800 
6801 			// In the fourth case, none of three other cases hold: the containers of A and B
6802 			// are siblings or descendants of sibling nodes. In this case, A is before B if
6803 			// the container of A is before the container of B in a pre-order traversal of the
6804 			// Ranges' context tree and A is after B otherwise.
6805 			cmnRoot = dom.findCommonAncestor(containerA, containerB);
6806 			childA = containerA;
6807 
6808 			while (childA && childA.parentNode != cmnRoot)
6809 				childA = childA.parentNode;
6810 
6811 			if (!childA)
6812 				childA = cmnRoot;
6813 
6814 			childB = containerB;
6815 			while (childB && childB.parentNode != cmnRoot)
6816 				childB = childB.parentNode;
6817 
6818 			if (!childB)
6819 				childB = cmnRoot;
6820 
6821 			if (childA == childB)
6822 				return 0; // equal
6823 
6824 			n = cmnRoot.firstChild;
6825 			while (n) {
6826 				if (n == childA)
6827 					return -1; // before
6828 
6829 				if (n == childB)
6830 					return 1; // after
6831 
6832 				n = n.nextSibling;
6833 			}
6834 		};
6835 
6836 		function _setEndPoint(st, n, o) {
6837 			var ec, sc;
6838 
6839 			if (st) {
6840 				t[START_CONTAINER] = n;
6841 				t[START_OFFSET] = o;
6842 			} else {
6843 				t[END_CONTAINER] = n;
6844 				t[END_OFFSET] = o;
6845 			}
6846 
6847 			// If one boundary-point of a Range is set to have a root container
6848 			// other than the current one for the Range, the Range is collapsed to
6849 			// the new position. This enforces the restriction that both boundary-
6850 			// points of a Range must have the same root container.
6851 			ec = t[END_CONTAINER];
6852 			while (ec.parentNode)
6853 				ec = ec.parentNode;
6854 
6855 			sc = t[START_CONTAINER];
6856 			while (sc.parentNode)
6857 				sc = sc.parentNode;
6858 
6859 			if (sc == ec) {
6860 				// The start position of a Range is guaranteed to never be after the
6861 				// end position. To enforce this restriction, if the start is set to
6862 				// be at a position after the end, the Range is collapsed to that
6863 				// position.
6864 				if (_compareBoundaryPoints(t[START_CONTAINER], t[START_OFFSET], t[END_CONTAINER], t[END_OFFSET]) > 0)
6865 					t.collapse(st);
6866 			} else
6867 				t.collapse(st);
6868 
6869 			t.collapsed = _isCollapsed();
6870 			t.commonAncestorContainer = dom.findCommonAncestor(t[START_CONTAINER], t[END_CONTAINER]);
6871 		};
6872 
6873 		function _traverse(how) {
6874 			var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep;
6875 
6876 			if (t[START_CONTAINER] == t[END_CONTAINER])
6877 				return _traverseSameContainer(how);
6878 
6879 			for (c = t[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
6880 				if (p == t[START_CONTAINER])
6881 					return _traverseCommonStartContainer(c, how);
6882 
6883 				++endContainerDepth;
6884 			}
6885 
6886 			for (c = t[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
6887 				if (p == t[END_CONTAINER])
6888 					return _traverseCommonEndContainer(c, how);
6889 
6890 				++startContainerDepth;
6891 			}
6892 
6893 			depthDiff = startContainerDepth - endContainerDepth;
6894 
6895 			startNode = t[START_CONTAINER];
6896 			while (depthDiff > 0) {
6897 				startNode = startNode.parentNode;
6898 				depthDiff--;
6899 			}
6900 
6901 			endNode = t[END_CONTAINER];
6902 			while (depthDiff < 0) {
6903 				endNode = endNode.parentNode;
6904 				depthDiff++;
6905 			}
6906 
6907 			// ascend the ancestor hierarchy until we have a common parent.
6908 			for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) {
6909 				startNode = sp;
6910 				endNode = ep;
6911 			}
6912 
6913 			return _traverseCommonAncestors(startNode, endNode, how);
6914 		};
6915 
6916 		 function _traverseSameContainer(how) {
6917 			var frag, s, sub, n, cnt, sibling, xferNode, start, len;
6918 
6919 			if (how != DELETE)
6920 				frag = createDocumentFragment();
6921 
6922 			// If selection is empty, just return the fragment
6923 			if (t[START_OFFSET] == t[END_OFFSET])
6924 				return frag;
6925 
6926 			// Text node needs special case handling
6927 			if (t[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) {
6928 				// get the substring
6929 				s = t[START_CONTAINER].nodeValue;
6930 				sub = s.substring(t[START_OFFSET], t[END_OFFSET]);
6931 
6932 				// set the original text node to its new value
6933 				if (how != CLONE) {
6934 					n = t[START_CONTAINER];
6935 					start = t[START_OFFSET];
6936 					len = t[END_OFFSET] - t[START_OFFSET];
6937 
6938 					if (start === 0 && len >= n.nodeValue.length - 1) {
6939 						n.parentNode.removeChild(n);
6940 					} else {
6941 						n.deleteData(start, len);
6942 					}
6943 
6944 					// Nothing is partially selected, so collapse to start point
6945 					t.collapse(TRUE);
6946 				}
6947 
6948 				if (how == DELETE)
6949 					return;
6950 
6951 				if (sub.length > 0) {
6952 					frag.appendChild(doc.createTextNode(sub));
6953 				}
6954 
6955 				return frag;
6956 			}
6957 
6958 			// Copy nodes between the start/end offsets.
6959 			n = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]);
6960 			cnt = t[END_OFFSET] - t[START_OFFSET];
6961 
6962 			while (n && cnt > 0) {
6963 				sibling = n.nextSibling;
6964 				xferNode = _traverseFullySelected(n, how);
6965 
6966 				if (frag)
6967 					frag.appendChild( xferNode );
6968 
6969 				--cnt;
6970 				n = sibling;
6971 			}
6972 
6973 			// Nothing is partially selected, so collapse to start point
6974 			if (how != CLONE)
6975 				t.collapse(TRUE);
6976 
6977 			return frag;
6978 		};
6979 
6980 		function _traverseCommonStartContainer(endAncestor, how) {
6981 			var frag, n, endIdx, cnt, sibling, xferNode;
6982 
6983 			if (how != DELETE)
6984 				frag = createDocumentFragment();
6985 
6986 			n = _traverseRightBoundary(endAncestor, how);
6987 
6988 			if (frag)
6989 				frag.appendChild(n);
6990 
6991 			endIdx = nodeIndex(endAncestor);
6992 			cnt = endIdx - t[START_OFFSET];
6993 
6994 			if (cnt <= 0) {
6995 				// Collapse to just before the endAncestor, which
6996 				// is partially selected.
6997 				if (how != CLONE) {
6998 					t.setEndBefore(endAncestor);
6999 					t.collapse(FALSE);
7000 				}
7001 
7002 				return frag;
7003 			}
7004 
7005 			n = endAncestor.previousSibling;
7006 			while (cnt > 0) {
7007 				sibling = n.previousSibling;
7008 				xferNode = _traverseFullySelected(n, how);
7009 
7010 				if (frag)
7011 					frag.insertBefore(xferNode, frag.firstChild);
7012 
7013 				--cnt;
7014 				n = sibling;
7015 			}
7016 
7017 			// Collapse to just before the endAncestor, which
7018 			// is partially selected.
7019 			if (how != CLONE) {
7020 				t.setEndBefore(endAncestor);
7021 				t.collapse(FALSE);
7022 			}
7023 
7024 			return frag;
7025 		};
7026 
7027 		function _traverseCommonEndContainer(startAncestor, how) {
7028 			var frag, startIdx, n, cnt, sibling, xferNode;
7029 
7030 			if (how != DELETE)
7031 				frag = createDocumentFragment();
7032 
7033 			n = _traverseLeftBoundary(startAncestor, how);
7034 			if (frag)
7035 				frag.appendChild(n);
7036 
7037 			startIdx = nodeIndex(startAncestor);
7038 			++startIdx; // Because we already traversed it
7039 
7040 			cnt = t[END_OFFSET] - startIdx;
7041 			n = startAncestor.nextSibling;
7042 			while (n && cnt > 0) {
7043 				sibling = n.nextSibling;
7044 				xferNode = _traverseFullySelected(n, how);
7045 
7046 				if (frag)
7047 					frag.appendChild(xferNode);
7048 
7049 				--cnt;
7050 				n = sibling;
7051 			}
7052 
7053 			if (how != CLONE) {
7054 				t.setStartAfter(startAncestor);
7055 				t.collapse(TRUE);
7056 			}
7057 
7058 			return frag;
7059 		};
7060 
7061 		function _traverseCommonAncestors(startAncestor, endAncestor, how) {
7062 			var n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling;
7063 
7064 			if (how != DELETE)
7065 				frag = createDocumentFragment();
7066 
7067 			n = _traverseLeftBoundary(startAncestor, how);
7068 			if (frag)
7069 				frag.appendChild(n);
7070 
7071 			commonParent = startAncestor.parentNode;
7072 			startOffset = nodeIndex(startAncestor);
7073 			endOffset = nodeIndex(endAncestor);
7074 			++startOffset;
7075 
7076 			cnt = endOffset - startOffset;
7077 			sibling = startAncestor.nextSibling;
7078 
7079 			while (cnt > 0) {
7080 				nextSibling = sibling.nextSibling;
7081 				n = _traverseFullySelected(sibling, how);
7082 
7083 				if (frag)
7084 					frag.appendChild(n);
7085 
7086 				sibling = nextSibling;
7087 				--cnt;
7088 			}
7089 
7090 			n = _traverseRightBoundary(endAncestor, how);
7091 
7092 			if (frag)
7093 				frag.appendChild(n);
7094 
7095 			if (how != CLONE) {
7096 				t.setStartAfter(startAncestor);
7097 				t.collapse(TRUE);
7098 			}
7099 
7100 			return frag;
7101 		};
7102 
7103 		function _traverseRightBoundary(root, how) {
7104 			var next = _getSelectedNode(t[END_CONTAINER], t[END_OFFSET] - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != t[END_CONTAINER];
7105 
7106 			if (next == root)
7107 				return _traverseNode(next, isFullySelected, FALSE, how);
7108 
7109 			parent = next.parentNode;
7110 			clonedParent = _traverseNode(parent, FALSE, FALSE, how);
7111 
7112 			while (parent) {
7113 				while (next) {
7114 					prevSibling = next.previousSibling;
7115 					clonedChild = _traverseNode(next, isFullySelected, FALSE, how);
7116 
7117 					if (how != DELETE)
7118 						clonedParent.insertBefore(clonedChild, clonedParent.firstChild);
7119 
7120 					isFullySelected = TRUE;
7121 					next = prevSibling;
7122 				}
7123 
7124 				if (parent == root)
7125 					return clonedParent;
7126 
7127 				next = parent.previousSibling;
7128 				parent = parent.parentNode;
7129 
7130 				clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how);
7131 
7132 				if (how != DELETE)
7133 					clonedGrandParent.appendChild(clonedParent);
7134 
7135 				clonedParent = clonedGrandParent;
7136 			}
7137 		};
7138 
7139 		function _traverseLeftBoundary(root, how) {
7140 			var next = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]), isFullySelected = next != t[START_CONTAINER], parent, clonedParent, nextSibling, clonedChild, clonedGrandParent;
7141 
7142 			if (next == root)
7143 				return _traverseNode(next, isFullySelected, TRUE, how);
7144 
7145 			parent = next.parentNode;
7146 			clonedParent = _traverseNode(parent, FALSE, TRUE, how);
7147 
7148 			while (parent) {
7149 				while (next) {
7150 					nextSibling = next.nextSibling;
7151 					clonedChild = _traverseNode(next, isFullySelected, TRUE, how);
7152 
7153 					if (how != DELETE)
7154 						clonedParent.appendChild(clonedChild);
7155 
7156 					isFullySelected = TRUE;
7157 					next = nextSibling;
7158 				}
7159 
7160 				if (parent == root)
7161 					return clonedParent;
7162 
7163 				next = parent.nextSibling;
7164 				parent = parent.parentNode;
7165 
7166 				clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how);
7167 
7168 				if (how != DELETE)
7169 					clonedGrandParent.appendChild(clonedParent);
7170 
7171 				clonedParent = clonedGrandParent;
7172 			}
7173 		};
7174 
7175 		function _traverseNode(n, isFullySelected, isLeft, how) {
7176 			var txtValue, newNodeValue, oldNodeValue, offset, newNode;
7177 
7178 			if (isFullySelected)
7179 				return _traverseFullySelected(n, how);
7180 
7181 			if (n.nodeType == 3 /* TEXT_NODE */) {
7182 				txtValue = n.nodeValue;
7183 
7184 				if (isLeft) {
7185 					offset = t[START_OFFSET];
7186 					newNodeValue = txtValue.substring(offset);
7187 					oldNodeValue = txtValue.substring(0, offset);
7188 				} else {
7189 					offset = t[END_OFFSET];
7190 					newNodeValue = txtValue.substring(0, offset);
7191 					oldNodeValue = txtValue.substring(offset);
7192 				}
7193 
7194 				if (how != CLONE)
7195 					n.nodeValue = oldNodeValue;
7196 
7197 				if (how == DELETE)
7198 					return;
7199 
7200 				newNode = dom.clone(n, FALSE);
7201 				newNode.nodeValue = newNodeValue;
7202 
7203 				return newNode;
7204 			}
7205 
7206 			if (how == DELETE)
7207 				return;
7208 
7209 			return dom.clone(n, FALSE);
7210 		};
7211 
7212 		function _traverseFullySelected(n, how) {
7213 			if (how != DELETE)
7214 				return how == CLONE ? dom.clone(n, TRUE) : n;
7215 
7216 			n.parentNode.removeChild(n);
7217 		};
7218 
7219 		function toStringIE() {
7220 			return dom.create('body', null, cloneContents()).outerText;
7221 		}
7222 		
7223 		return t;
7224 	};
7225 
7226 	ns.Range = Range;
7227 
7228 	// Older IE versions doesn't let you override toString by it's constructor so we have to stick it in the prototype
7229 	Range.prototype.toString = function() {
7230 		return this.toStringIE();
7231 	};
7232 })(tinymce.dom);
7233 
7234 (function() {
7235 	function Selection(selection) {
7236 		var self = this, dom = selection.dom, TRUE = true, FALSE = false;
7237 
7238 		function getPosition(rng, start) {
7239 			var checkRng, startIndex = 0, endIndex, inside,
7240 				children, child, offset, index, position = -1, parent;
7241 
7242 			// Setup test range, collapse it and get the parent
7243 			checkRng = rng.duplicate();
7244 			checkRng.collapse(start);
7245 			parent = checkRng.parentElement();
7246 
7247 			// Check if the selection is within the right document
7248 			if (parent.ownerDocument !== selection.dom.doc)
7249 				return;
7250 
7251 			// IE will report non editable elements as it's parent so look for an editable one
7252 			while (parent.contentEditable === "false") {
7253 				parent = parent.parentNode;
7254 			}
7255 
7256 			// If parent doesn't have any children then return that we are inside the element
7257 			if (!parent.hasChildNodes()) {
7258 				return {node : parent, inside : 1};
7259 			}
7260 
7261 			// Setup node list and endIndex
7262 			children = parent.children;
7263 			endIndex = children.length - 1;
7264 
7265 			// Perform a binary search for the position
7266 			while (startIndex <= endIndex) {
7267 				index = Math.floor((startIndex + endIndex) / 2);
7268 
7269 				// Move selection to node and compare the ranges
7270 				child = children[index];
7271 				checkRng.moveToElementText(child);
7272 				position = checkRng.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', rng);
7273 
7274 				// Before/after or an exact match
7275 				if (position > 0) {
7276 					endIndex = index - 1;
7277 				} else if (position < 0) {
7278 					startIndex = index + 1;
7279 				} else {
7280 					return {node : child};
7281 				}
7282 			}
7283 
7284 			// Check if child position is before or we didn't find a position
7285 			if (position < 0) {
7286 				// No element child was found use the parent element and the offset inside that
7287 				if (!child) {
7288 					checkRng.moveToElementText(parent);
7289 					checkRng.collapse(true);
7290 					child = parent;
7291 					inside = true;
7292 				} else
7293 					checkRng.collapse(false);
7294 
7295 				// Walk character by character in text node until we hit the selected range endpoint, hit the end of document or parent isn't the right one
7296 				// We need to walk char by char since rng.text or rng.htmlText will trim line endings
7297 				offset = 0;
7298 				while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) {
7299 					if (checkRng.move('character', 1) === 0 || parent != checkRng.parentElement()) {
7300 						break;
7301 					}
7302 
7303 					offset++;
7304 				}
7305 			} else {
7306 				// Child position is after the selection endpoint
7307 				checkRng.collapse(true);
7308 
7309 				// Walk character by character in text node until we hit the selected range endpoint, hit the end of document or parent isn't the right one
7310 				offset = 0;
7311 				while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) {
7312 					if (checkRng.move('character', -1) === 0 || parent != checkRng.parentElement()) {
7313 						break;
7314 					}
7315 
7316 					offset++;
7317 				}
7318 			}
7319 
7320 			return {node : child, position : position, offset : offset, inside : inside};
7321 		};
7322 
7323 		// Returns a W3C DOM compatible range object by using the IE Range API
7324 		function getRange() {
7325 			var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed, tmpRange, element2, bookmark, fail;
7326 
7327 			// If selection is outside the current document just return an empty range
7328 			element = ieRange.item ? ieRange.item(0) : ieRange.parentElement();
7329 			if (element.ownerDocument != dom.doc)
7330 				return domRange;
7331 
7332 			collapsed = selection.isCollapsed();
7333 
7334 			// Handle control selection
7335 			if (ieRange.item) {
7336 				domRange.setStart(element.parentNode, dom.nodeIndex(element));
7337 				domRange.setEnd(domRange.startContainer, domRange.startOffset + 1);
7338 
7339 				return domRange;
7340 			}
7341 
7342 			function findEndPoint(start) {
7343 				var endPoint = getPosition(ieRange, start), container, offset, textNodeOffset = 0, sibling, undef, nodeValue;
7344 
7345 				container = endPoint.node;
7346 				offset = endPoint.offset;
7347 
7348 				if (endPoint.inside && !container.hasChildNodes()) {
7349 					domRange[start ? 'setStart' : 'setEnd'](container, 0);
7350 					return;
7351 				}
7352 
7353 				if (offset === undef) {
7354 					domRange[start ? 'setStartBefore' : 'setEndAfter'](container);
7355 					return;
7356 				}
7357 
7358 				if (endPoint.position < 0) {
7359 					sibling = endPoint.inside ? container.firstChild : container.nextSibling;
7360 
7361 					if (!sibling) {
7362 						domRange[start ? 'setStartAfter' : 'setEndAfter'](container);
7363 						return;
7364 					}
7365 
7366 					if (!offset) {
7367 						if (sibling.nodeType == 3)
7368 							domRange[start ? 'setStart' : 'setEnd'](sibling, 0);
7369 						else
7370 							domRange[start ? 'setStartBefore' : 'setEndBefore'](sibling);
7371 
7372 						return;
7373 					}
7374 
7375 					// Find the text node and offset
7376 					while (sibling) {
7377 						nodeValue = sibling.nodeValue;
7378 						textNodeOffset += nodeValue.length;
7379 
7380 						// We are at or passed the position we where looking for
7381 						if (textNodeOffset >= offset) {
7382 							container = sibling;
7383 							textNodeOffset -= offset;
7384 							textNodeOffset = nodeValue.length - textNodeOffset;
7385 							break;
7386 						}
7387 
7388 						sibling = sibling.nextSibling;
7389 					}
7390 				} else {
7391 					// Find the text node and offset
7392 					sibling = container.previousSibling;
7393 
7394 					if (!sibling)
7395 						return domRange[start ? 'setStartBefore' : 'setEndBefore'](container);
7396 
7397 					// If there isn't any text to loop then use the first position
7398 					if (!offset) {
7399 						if (container.nodeType == 3)
7400 							domRange[start ? 'setStart' : 'setEnd'](sibling, container.nodeValue.length);
7401 						else
7402 							domRange[start ? 'setStartAfter' : 'setEndAfter'](sibling);
7403 
7404 						return;
7405 					}
7406 
7407 					while (sibling) {
7408 						textNodeOffset += sibling.nodeValue.length;
7409 
7410 						// We are at or passed the position we where looking for
7411 						if (textNodeOffset >= offset) {
7412 							container = sibling;
7413 							textNodeOffset -= offset;
7414 							break;
7415 						}
7416 
7417 						sibling = sibling.previousSibling;
7418 					}
7419 				}
7420 
7421 				domRange[start ? 'setStart' : 'setEnd'](container, textNodeOffset);
7422 			};
7423 
7424 			try {
7425 				// Find start point
7426 				findEndPoint(true);
7427 
7428 				// Find end point if needed
7429 				if (!collapsed)
7430 					findEndPoint();
7431 			} catch (ex) {
7432 				// IE has a nasty bug where text nodes might throw "invalid argument" when you
7433 				// access the nodeValue or other properties of text nodes. This seems to happend when
7434 				// text nodes are split into two nodes by a delete/backspace call. So lets detect it and try to fix it.
7435 				if (ex.number == -2147024809) {
7436 					// Get the current selection
7437 					bookmark = self.getBookmark(2);
7438 
7439 					// Get start element
7440 					tmpRange = ieRange.duplicate();
7441 					tmpRange.collapse(true);
7442 					element = tmpRange.parentElement();
7443 
7444 					// Get end element
7445 					if (!collapsed) {
7446 						tmpRange = ieRange.duplicate();
7447 						tmpRange.collapse(false);
7448 						element2 = tmpRange.parentElement();
7449 						element2.innerHTML = element2.innerHTML;
7450 					}
7451 
7452 					// Remove the broken elements
7453 					element.innerHTML = element.innerHTML;
7454 
7455 					// Restore the selection
7456 					self.moveToBookmark(bookmark);
7457 
7458 					// Since the range has moved we need to re-get it
7459 					ieRange = selection.getRng();
7460 
7461 					// Find start point
7462 					findEndPoint(true);
7463 
7464 					// Find end point if needed
7465 					if (!collapsed)
7466 						findEndPoint();
7467 				} else
7468 					throw ex; // Throw other errors
7469 			}
7470 
7471 			return domRange;
7472 		};
7473 
7474 		this.getBookmark = function(type) {
7475 			var rng = selection.getRng(), start, end, bookmark = {};
7476 
7477 			function getIndexes(node) {
7478 				var parent, root, children, i, indexes = [];
7479 
7480 				parent = node.parentNode;
7481 				root = dom.getRoot().parentNode;
7482 
7483 				while (parent != root && parent.nodeType !== 9) {
7484 					children = parent.children;
7485 
7486 					i = children.length;
7487 					while (i--) {
7488 						if (node === children[i]) {
7489 							indexes.push(i);
7490 							break;
7491 						}
7492 					}
7493 
7494 					node = parent;
7495 					parent = parent.parentNode;
7496 				}
7497 
7498 				return indexes;
7499 			};
7500 
7501 			function getBookmarkEndPoint(start) {
7502 				var position;
7503 
7504 				position = getPosition(rng, start);
7505 				if (position) {
7506 					return {
7507 						position : position.position,
7508 						offset : position.offset,
7509 						indexes : getIndexes(position.node),
7510 						inside : position.inside
7511 					};
7512 				}
7513 			};
7514 
7515 			// Non ubstructive bookmark
7516 			if (type === 2) {
7517 				// Handle text selection
7518 				if (!rng.item) {
7519 					bookmark.start = getBookmarkEndPoint(true);
7520 
7521 					if (!selection.isCollapsed())
7522 						bookmark.end = getBookmarkEndPoint();
7523 				} else
7524 					bookmark.start = {ctrl : true, indexes : getIndexes(rng.item(0))};
7525 			}
7526 
7527 			return bookmark;
7528 		};
7529 
7530 		this.moveToBookmark = function(bookmark) {
7531 			var rng, body = dom.doc.body;
7532 
7533 			function resolveIndexes(indexes) {
7534 				var node, i, idx, children;
7535 
7536 				node = dom.getRoot();
7537 				for (i = indexes.length - 1; i >= 0; i--) {
7538 					children = node.children;
7539 					idx = indexes[i];
7540 
7541 					if (idx <= children.length - 1) {
7542 						node = children[idx];
7543 					}
7544 				}
7545 
7546 				return node;
7547 			};
7548 			
7549 			function setBookmarkEndPoint(start) {
7550 				var endPoint = bookmark[start ? 'start' : 'end'], moveLeft, moveRng, undef;
7551 
7552 				if (endPoint) {
7553 					moveLeft = endPoint.position > 0;
7554 
7555 					moveRng = body.createTextRange();
7556 					moveRng.moveToElementText(resolveIndexes(endPoint.indexes));
7557 
7558 					offset = endPoint.offset;
7559 					if (offset !== undef) {
7560 						moveRng.collapse(endPoint.inside || moveLeft);
7561 						moveRng.moveStart('character', moveLeft ? -offset : offset);
7562 					} else
7563 						moveRng.collapse(start);
7564 
7565 					rng.setEndPoint(start ? 'StartToStart' : 'EndToStart', moveRng);
7566 
7567 					if (start)
7568 						rng.collapse(true);
7569 				}
7570 			};
7571 
7572 			if (bookmark.start) {
7573 				if (bookmark.start.ctrl) {
7574 					rng = body.createControlRange();
7575 					rng.addElement(resolveIndexes(bookmark.start.indexes));
7576 					rng.select();
7577 				} else {
7578 					rng = body.createTextRange();
7579 					setBookmarkEndPoint(true);
7580 					setBookmarkEndPoint();
7581 					rng.select();
7582 				}
7583 			}
7584 		};
7585 
7586 		this.addRange = function(rng) {
7587 			var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, sibling, doc = selection.dom.doc, body = doc.body;
7588 
7589 			function setEndPoint(start) {
7590 				var container, offset, marker, tmpRng, nodes;
7591 
7592 				marker = dom.create('a');
7593 				container = start ? startContainer : endContainer;
7594 				offset = start ? startOffset : endOffset;
7595 				tmpRng = ieRng.duplicate();
7596 
7597 				if (container == doc || container == doc.documentElement) {
7598 					container = body;
7599 					offset = 0;
7600 				}
7601 
7602 				if (container.nodeType == 3) {
7603 					container.parentNode.insertBefore(marker, container);
7604 					tmpRng.moveToElementText(marker);
7605 					tmpRng.moveStart('character', offset);
7606 					dom.remove(marker);
7607 					ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
7608 				} else {
7609 					nodes = container.childNodes;
7610 
7611 					if (nodes.length) {
7612 						if (offset >= nodes.length) {
7613 							dom.insertAfter(marker, nodes[nodes.length - 1]);
7614 						} else {
7615 							container.insertBefore(marker, nodes[offset]);
7616 						}
7617 
7618 						tmpRng.moveToElementText(marker);
7619 					} else if (container.canHaveHTML) {
7620 						// Empty node selection for example <div>|</div>
7621 						// Setting innerHTML with a span marker then remove that marker seems to keep empty block elements open
7622 						container.innerHTML = '<span>\uFEFF</span>';
7623 						marker = container.firstChild;
7624 						tmpRng.moveToElementText(marker);
7625 						tmpRng.collapse(FALSE); // Collapse false works better than true for some odd reason
7626 					}
7627 
7628 					ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
7629 					dom.remove(marker);
7630 				}
7631 			}
7632 
7633 			// Setup some shorter versions
7634 			startContainer = rng.startContainer;
7635 			startOffset = rng.startOffset;
7636 			endContainer = rng.endContainer;
7637 			endOffset = rng.endOffset;
7638 			ieRng = body.createTextRange();
7639 
7640 			// If single element selection then try making a control selection out of it
7641 			if (startContainer == endContainer && startContainer.nodeType == 1) {
7642 				// Trick to place the caret inside an empty block element like <p></p>
7643 				if (startOffset == endOffset && !startContainer.hasChildNodes()) {
7644 					if (startContainer.canHaveHTML) {
7645 						// Check if previous sibling is an empty block if it is then we need to render it
7646 						// IE would otherwise move the caret into the sibling instead of the empty startContainer see: #5236
7647 						// Example this: <p></p><p>|</p> would become this: <p>|</p><p></p>
7648 						sibling = startContainer.previousSibling;
7649 						if (sibling && !sibling.hasChildNodes() && dom.isBlock(sibling)) {
7650 							sibling.innerHTML = '\uFEFF';
7651 						} else {
7652 							sibling = null;
7653 						}
7654 
7655 						startContainer.innerHTML = '<span>\uFEFF</span><span>\uFEFF</span>';
7656 						ieRng.moveToElementText(startContainer.lastChild);
7657 						ieRng.select();
7658 						dom.doc.selection.clear();
7659 						startContainer.innerHTML = '';
7660 
7661 						if (sibling) {
7662 							sibling.innerHTML = '';
7663 						}
7664 						return;
7665 					} else {
7666 						startOffset = dom.nodeIndex(startContainer);
7667 						startContainer = startContainer.parentNode;
7668 					}
7669 				}
7670 
7671 				if (startOffset == endOffset - 1) {
7672 					try {
7673 						ctrlRng = body.createControlRange();
7674 						ctrlRng.addElement(startContainer.childNodes[startOffset]);
7675 						ctrlRng.select();
7676 						return;
7677 					} catch (ex) {
7678 						// Ignore
7679 					}
7680 				}
7681 			}
7682 
7683 			// Set start/end point of selection
7684 			setEndPoint(true);
7685 			setEndPoint();
7686 
7687 			// Select the new range and scroll it into view
7688 			ieRng.select();
7689 		};
7690 
7691 		// Expose range method
7692 		this.getRangeAt = getRange;
7693 	};
7694 
7695 	// Expose the selection object
7696 	tinymce.dom.TridentSelection = Selection;
7697 })();
7698 
7699 
7700 (function(tinymce) {
7701 	tinymce.dom.Element = function(id, settings) {
7702 		var t = this, dom, el;
7703 
7704 		t.settings = settings = settings || {};
7705 		t.id = id;
7706 		t.dom = dom = settings.dom || tinymce.DOM;
7707 
7708 		// Only IE leaks DOM references, this is a lot faster
7709 		if (!tinymce.isIE)
7710 			el = dom.get(t.id);
7711 
7712 		tinymce.each(
7713 				('getPos,getRect,getParent,add,setStyle,getStyle,setStyles,' + 
7714 				'setAttrib,setAttribs,getAttrib,addClass,removeClass,' + 
7715 				'hasClass,getOuterHTML,setOuterHTML,remove,show,hide,' + 
7716 				'isHidden,setHTML,get').split(/,/), function(k) {
7717 					t[k] = function() {
7718 						var a = [id], i;
7719 
7720 						for (i = 0; i < arguments.length; i++)
7721 							a.push(arguments[i]);
7722 
7723 						a = dom[k].apply(dom, a);
7724 						t.update(k);
7725 
7726 						return a;
7727 					};
7728 			}
7729 		);
7730 
7731 		tinymce.extend(t, {
7732 			on : function(n, f, s) {
7733 				return tinymce.dom.Event.add(t.id, n, f, s);
7734 			},
7735 
7736 			getXY : function() {
7737 				return {
7738 					x : parseInt(t.getStyle('left')),
7739 					y : parseInt(t.getStyle('top'))
7740 				};
7741 			},
7742 
7743 			getSize : function() {
7744 				var n = dom.get(t.id);
7745 
7746 				return {
7747 					w : parseInt(t.getStyle('width') || n.clientWidth),
7748 					h : parseInt(t.getStyle('height') || n.clientHeight)
7749 				};
7750 			},
7751 
7752 			moveTo : function(x, y) {
7753 				t.setStyles({left : x, top : y});
7754 			},
7755 
7756 			moveBy : function(x, y) {
7757 				var p = t.getXY();
7758 
7759 				t.moveTo(p.x + x, p.y + y);
7760 			},
7761 
7762 			resizeTo : function(w, h) {
7763 				t.setStyles({width : w, height : h});
7764 			},
7765 
7766 			resizeBy : function(w, h) {
7767 				var s = t.getSize();
7768 
7769 				t.resizeTo(s.w + w, s.h + h);
7770 			},
7771 
7772 			update : function(k) {
7773 				var b;
7774 
7775 				if (tinymce.isIE6 && settings.blocker) {
7776 					k = k || '';
7777 
7778 					// Ignore getters
7779 					if (k.indexOf('get') === 0 || k.indexOf('has') === 0 || k.indexOf('is') === 0)
7780 						return;
7781 
7782 					// Remove blocker on remove
7783 					if (k == 'remove') {
7784 						dom.remove(t.blocker);
7785 						return;
7786 					}
7787 
7788 					if (!t.blocker) {
7789 						t.blocker = dom.uniqueId();
7790 						b = dom.add(settings.container || dom.getRoot(), 'iframe', {id : t.blocker, style : 'position:absolute;', frameBorder : 0, src : 'javascript:""'});
7791 						dom.setStyle(b, 'opacity', 0);
7792 					} else
7793 						b = dom.get(t.blocker);
7794 
7795 					dom.setStyles(b, {
7796 						left : t.getStyle('left', 1),
7797 						top : t.getStyle('top', 1),
7798 						width : t.getStyle('width', 1),
7799 						height : t.getStyle('height', 1),
7800 						display : t.getStyle('display', 1),
7801 						zIndex : parseInt(t.getStyle('zIndex', 1) || 0) - 1
7802 					});
7803 				}
7804 			}
7805 		});
7806 	};
7807 })(tinymce);
7808 
7809 (function(tinymce) {
7810 	function trimNl(s) {
7811 		return s.replace(/[\n\r]+/g, '');
7812 	};
7813 
7814 	// Shorten names
7815 	var is = tinymce.is, isIE = tinymce.isIE, each = tinymce.each, TreeWalker = tinymce.dom.TreeWalker;
7816 
7817 	tinymce.create('tinymce.dom.Selection', {
7818 		Selection : function(dom, win, serializer, editor) {
7819 			var t = this;
7820 
7821 			t.dom = dom;
7822 			t.win = win;
7823 			t.serializer = serializer;
7824 			t.editor = editor;
7825 
7826 			// Add events
7827 			each([
7828 				'onBeforeSetContent',
7829 
7830 				'onBeforeGetContent',
7831 
7832 				'onSetContent',
7833 
7834 				'onGetContent'
7835 			], function(e) {
7836 				t[e] = new tinymce.util.Dispatcher(t);
7837 			});
7838 
7839 			// No W3C Range support
7840 			if (!t.win.getSelection)
7841 				t.tridentSel = new tinymce.dom.TridentSelection(t);
7842 
7843 			if (tinymce.isIE && dom.boxModel)
7844 				this._fixIESelection();
7845 
7846 			// Prevent leaks
7847 			tinymce.addUnload(t.destroy, t);
7848 		},
7849 
7850 		setCursorLocation: function(node, offset) {
7851 			var t = this; var r = t.dom.createRng();
7852 			r.setStart(node, offset);
7853 			r.setEnd(node, offset);
7854 			t.setRng(r);
7855 			t.collapse(false);
7856 		},
7857 		getContent : function(s) {
7858 			var t = this, r = t.getRng(), e = t.dom.create("body"), se = t.getSel(), wb, wa, n;
7859 
7860 			s = s || {};
7861 			wb = wa = '';
7862 			s.get = true;
7863 			s.format = s.format || 'html';
7864 			s.forced_root_block = '';
7865 			t.onBeforeGetContent.dispatch(t, s);
7866 
7867 			if (s.format == 'text')
7868 				return t.isCollapsed() ? '' : (r.text || (se.toString ? se.toString() : ''));
7869 
7870 			if (r.cloneContents) {
7871 				n = r.cloneContents();
7872 
7873 				if (n)
7874 					e.appendChild(n);
7875 			} else if (is(r.item) || is(r.htmlText)) {
7876 				// IE will produce invalid markup if elements are present that
7877 				// it doesn't understand like custom elements or HTML5 elements.
7878 				// Adding a BR in front of the contents and then remoiving it seems to fix it though.
7879 				e.innerHTML = '<br>' + (r.item ? r.item(0).outerHTML : r.htmlText);
7880 				e.removeChild(e.firstChild);
7881 			} else
7882 				e.innerHTML = r.toString();
7883 
7884 			// Keep whitespace before and after
7885 			if (/^\s/.test(e.innerHTML))
7886 				wb = ' ';
7887 
7888 			if (/\s+$/.test(e.innerHTML))
7889 				wa = ' ';
7890 
7891 			s.getInner = true;
7892 
7893 			s.content = t.isCollapsed() ? '' : wb + t.serializer.serialize(e, s) + wa;
7894 			t.onGetContent.dispatch(t, s);
7895 
7896 			return s.content;
7897 		},
7898 
7899 		setContent : function(content, args) {
7900 			var self = this, rng = self.getRng(), caretNode, doc = self.win.document, frag, temp;
7901 
7902 			args = args || {format : 'html'};
7903 			args.set = true;
7904 			content = args.content = content;
7905 
7906 			// Dispatch before set content event
7907 			if (!args.no_events)
7908 				self.onBeforeSetContent.dispatch(self, args);
7909 
7910 			content = args.content;
7911 
7912 			if (rng.insertNode) {
7913 				// Make caret marker since insertNode places the caret in the beginning of text after insert
7914 				content += '<span id="__caret">_</span>';
7915 
7916 				// Delete and insert new node
7917 				if (rng.startContainer == doc && rng.endContainer == doc) {
7918 					// WebKit will fail if the body is empty since the range is then invalid and it can't insert contents
7919 					doc.body.innerHTML = content;
7920 				} else {
7921 					rng.deleteContents();
7922 
7923 					if (doc.body.childNodes.length === 0) {
7924 						doc.body.innerHTML = content;
7925 					} else {
7926 						// createContextualFragment doesn't exists in IE 9 DOMRanges
7927 						if (rng.createContextualFragment) {
7928 							rng.insertNode(rng.createContextualFragment(content));
7929 						} else {
7930 							// Fake createContextualFragment call in IE 9
7931 							frag = doc.createDocumentFragment();
7932 							temp = doc.createElement('div');
7933 
7934 							frag.appendChild(temp);
7935 							temp.outerHTML = content;
7936 
7937 							rng.insertNode(frag);
7938 						}
7939 					}
7940 				}
7941 
7942 				// Move to caret marker
7943 				caretNode = self.dom.get('__caret');
7944 
7945 				// Make sure we wrap it compleatly, Opera fails with a simple select call
7946 				rng = doc.createRange();
7947 				rng.setStartBefore(caretNode);
7948 				rng.setEndBefore(caretNode);
7949 				self.setRng(rng);
7950 
7951 				// Remove the caret position
7952 				self.dom.remove('__caret');
7953 
7954 				try {
7955 					self.setRng(rng);
7956 				} catch (ex) {
7957 					// Might fail on Opera for some odd reason
7958 				}
7959 			} else {
7960 				if (rng.item) {
7961 					// Delete content and get caret text selection
7962 					doc.execCommand('Delete', false, null);
7963 					rng = self.getRng();
7964 				}
7965 
7966 				// Explorer removes spaces from the beginning of pasted contents
7967 				if (/^\s+/.test(content)) {
7968 					rng.pasteHTML('<span id="__mce_tmp">_</span>' + content);
7969 					self.dom.remove('__mce_tmp');
7970 				} else
7971 					rng.pasteHTML(content);
7972 			}
7973 
7974 			// Dispatch set content event
7975 			if (!args.no_events)
7976 				self.onSetContent.dispatch(self, args);
7977 		},
7978 
7979 		getStart : function() {
7980 			var self = this, rng = self.getRng(), startElement, parentElement, checkRng, node;
7981 
7982 			if (rng.duplicate || rng.item) {
7983 				// Control selection, return first item
7984 				if (rng.item)
7985 					return rng.item(0);
7986 
7987 				// Get start element
7988 				checkRng = rng.duplicate();
7989 				checkRng.collapse(1);
7990 				startElement = checkRng.parentElement();
7991 				if (startElement.ownerDocument !== self.dom.doc) {
7992 					startElement = self.dom.getRoot();
7993 				}
7994 
7995 				// Check if range parent is inside the start element, then return the inner parent element
7996 				// This will fix issues when a single element is selected, IE would otherwise return the wrong start element
7997 				parentElement = node = rng.parentElement();
7998 				while (node = node.parentNode) {
7999 					if (node == startElement) {
8000 						startElement = parentElement;
8001 						break;
8002 					}
8003 				}
8004 
8005 				return startElement;
8006 			} else {
8007 				startElement = rng.startContainer;
8008 
8009 				if (startElement.nodeType == 1 && startElement.hasChildNodes())
8010 					startElement = startElement.childNodes[Math.min(startElement.childNodes.length - 1, rng.startOffset)];
8011 
8012 				if (startElement && startElement.nodeType == 3)
8013 					return startElement.parentNode;
8014 
8015 				return startElement;
8016 			}
8017 		},
8018 
8019 		getEnd : function() {
8020 			var self = this, rng = self.getRng(), endElement, endOffset;
8021 
8022 			if (rng.duplicate || rng.item) {
8023 				if (rng.item)
8024 					return rng.item(0);
8025 
8026 				rng = rng.duplicate();
8027 				rng.collapse(0);
8028 				endElement = rng.parentElement();
8029 				if (endElement.ownerDocument !== self.dom.doc) {
8030 					endElement = self.dom.getRoot();
8031 				}
8032 
8033 				if (endElement && endElement.nodeName == 'BODY')
8034 					return endElement.lastChild || endElement;
8035 
8036 				return endElement;
8037 			} else {
8038 				endElement = rng.endContainer;
8039 				endOffset = rng.endOffset;
8040 
8041 				if (endElement.nodeType == 1 && endElement.hasChildNodes())
8042 					endElement = endElement.childNodes[endOffset > 0 ? endOffset - 1 : endOffset];
8043 
8044 				if (endElement && endElement.nodeType == 3)
8045 					return endElement.parentNode;
8046 
8047 				return endElement;
8048 			}
8049 		},
8050 
8051 		getBookmark : function(type, normalized) {
8052 			var t = this, dom = t.dom, rng, rng2, id, collapsed, name, element, index, chr = '\uFEFF', styles;
8053 
8054 			function findIndex(name, element) {
8055 				var index = 0;
8056 
8057 				each(dom.select(name), function(node, i) {
8058 					if (node == element)
8059 						index = i;
8060 				});
8061 
8062 				return index;
8063 			};
8064 
8065 			function normalizeTableCellSelection(rng) {
8066 				function moveEndPoint(start) {
8067 					var container, offset, childNodes, prefix = start ? 'start' : 'end';
8068 
8069 					container = rng[prefix + 'Container'];
8070 					offset = rng[prefix + 'Offset'];
8071 
8072 					if (container.nodeType == 1 && container.nodeName == "TR") {
8073 						childNodes = container.childNodes;
8074 						container = childNodes[Math.min(start ? offset : offset - 1, childNodes.length - 1)];
8075 						if (container) {
8076 							offset = start ? 0 : container.childNodes.length;
8077 							rng['set' + (start ? 'Start' : 'End')](container, offset);
8078 						}
8079 					}
8080 				};
8081 
8082 				moveEndPoint(true);
8083 				moveEndPoint();
8084 
8085 				return rng;
8086 			};
8087 
8088 			function getLocation() {
8089 				var rng = t.getRng(true), root = dom.getRoot(), bookmark = {};
8090 
8091 				function getPoint(rng, start) {
8092 					var container = rng[start ? 'startContainer' : 'endContainer'],
8093 						offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0;
8094 
8095 					if (container.nodeType == 3) {
8096 						if (normalized) {
8097 							for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling)
8098 								offset += node.nodeValue.length;
8099 						}
8100 
8101 						point.push(offset);
8102 					} else {
8103 						childNodes = container.childNodes;
8104 
8105 						if (offset >= childNodes.length && childNodes.length) {
8106 							after = 1;
8107 							offset = Math.max(0, childNodes.length - 1);
8108 						}
8109 
8110 						point.push(t.dom.nodeIndex(childNodes[offset], normalized) + after);
8111 					}
8112 
8113 					for (; container && container != root; container = container.parentNode)
8114 						point.push(t.dom.nodeIndex(container, normalized));
8115 
8116 					return point;
8117 				};
8118 
8119 				bookmark.start = getPoint(rng, true);
8120 
8121 				if (!t.isCollapsed())
8122 					bookmark.end = getPoint(rng);
8123 
8124 				return bookmark;
8125 			};
8126 
8127 			if (type == 2) {
8128 				if (t.tridentSel)
8129 					return t.tridentSel.getBookmark(type);
8130 
8131 				return getLocation();
8132 			}
8133 
8134 			// Handle simple range
8135 			if (type)
8136 				return {rng : t.getRng()};
8137 
8138 			rng = t.getRng();
8139 			id = dom.uniqueId();
8140 			collapsed = tinyMCE.activeEditor.selection.isCollapsed();
8141 			styles = 'overflow:hidden;line-height:0px';
8142 
8143 			// Explorer method
8144 			if (rng.duplicate || rng.item) {
8145 				// Text selection
8146 				if (!rng.item) {
8147 					rng2 = rng.duplicate();
8148 
8149 					try {
8150 						// Insert start marker
8151 						rng.collapse();
8152 						rng.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>');
8153 
8154 						// Insert end marker
8155 						if (!collapsed) {
8156 							rng2.collapse(false);
8157 
8158 							// Detect the empty space after block elements in IE and move the end back one character <p></p>] becomes <p>]</p>
8159 							rng.moveToElementText(rng2.parentElement());
8160 							if (rng.compareEndPoints('StartToEnd', rng2) === 0)
8161 								rng2.move('character', -1);
8162 
8163 							rng2.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>');
8164 						}
8165 					} catch (ex) {
8166 						// IE might throw unspecified error so lets ignore it
8167 						return null;
8168 					}
8169 				} else {
8170 					// Control selection
8171 					element = rng.item(0);
8172 					name = element.nodeName;
8173 
8174 					return {name : name, index : findIndex(name, element)};
8175 				}
8176 			} else {
8177 				element = t.getNode();
8178 				name = element.nodeName;
8179 				if (name == 'IMG')
8180 					return {name : name, index : findIndex(name, element)};
8181 
8182 				// W3C method
8183 				rng2 = normalizeTableCellSelection(rng.cloneRange());
8184 
8185 				// Insert end marker
8186 				if (!collapsed) {
8187 					rng2.collapse(false);
8188 					rng2.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_end', style : styles}, chr));
8189 				}
8190 
8191 				rng = normalizeTableCellSelection(rng);
8192 				rng.collapse(true);
8193 				rng.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_start', style : styles}, chr));
8194 			}
8195 
8196 			t.moveToBookmark({id : id, keep : 1});
8197 
8198 			return {id : id};
8199 		},
8200 
8201 		moveToBookmark : function(bookmark) {
8202 			var t = this, dom = t.dom, marker1, marker2, rng, root, startContainer, endContainer, startOffset, endOffset;
8203 
8204 			function setEndPoint(start) {
8205 				var point = bookmark[start ? 'start' : 'end'], i, node, offset, children;
8206 
8207 				if (point) {
8208 					offset = point[0];
8209 
8210 					// Find container node
8211 					for (node = root, i = point.length - 1; i >= 1; i--) {
8212 						children = node.childNodes;
8213 
8214 						if (point[i] > children.length - 1)
8215 							return;
8216 
8217 						node = children[point[i]];
8218 					}
8219 
8220 					// Move text offset to best suitable location
8221 					if (node.nodeType === 3)
8222 						offset = Math.min(point[0], node.nodeValue.length);
8223 
8224 					// Move element offset to best suitable location
8225 					if (node.nodeType === 1)
8226 						offset = Math.min(point[0], node.childNodes.length);
8227 
8228 					// Set offset within container node
8229 					if (start)
8230 						rng.setStart(node, offset);
8231 					else
8232 						rng.setEnd(node, offset);
8233 				}
8234 
8235 				return true;
8236 			};
8237 
8238 			function restoreEndPoint(suffix) {
8239 				var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep;
8240 
8241 				if (marker) {
8242 					node = marker.parentNode;
8243 
8244 					if (suffix == 'start') {
8245 						if (!keep) {
8246 							idx = dom.nodeIndex(marker);
8247 						} else {
8248 							node = marker.firstChild;
8249 							idx = 1;
8250 						}
8251 
8252 						startContainer = endContainer = node;
8253 						startOffset = endOffset = idx;
8254 					} else {
8255 						if (!keep) {
8256 							idx = dom.nodeIndex(marker);
8257 						} else {
8258 							node = marker.firstChild;
8259 							idx = 1;
8260 						}
8261 
8262 						endContainer = node;
8263 						endOffset = idx;
8264 					}
8265 
8266 					if (!keep) {
8267 						prev = marker.previousSibling;
8268 						next = marker.nextSibling;
8269 
8270 						// Remove all marker text nodes
8271 						each(tinymce.grep(marker.childNodes), function(node) {
8272 							if (node.nodeType == 3)
8273 								node.nodeValue = node.nodeValue.replace(/\uFEFF/g, '');
8274 						});
8275 
8276 						// Remove marker but keep children if for example contents where inserted into the marker
8277 						// Also remove duplicated instances of the marker for example by a split operation or by WebKit auto split on paste feature
8278 						while (marker = dom.get(bookmark.id + '_' + suffix))
8279 							dom.remove(marker, 1);
8280 
8281 						// If siblings are text nodes then merge them unless it's Opera since it some how removes the node
8282 						// and we are sniffing since adding a lot of detection code for a browser with 3% of the market isn't worth the effort. Sorry, Opera but it's just a fact
8283 						if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !tinymce.isOpera) {
8284 							idx = prev.nodeValue.length;
8285 							prev.appendData(next.nodeValue);
8286 							dom.remove(next);
8287 
8288 							if (suffix == 'start') {
8289 								startContainer = endContainer = prev;
8290 								startOffset = endOffset = idx;
8291 							} else {
8292 								endContainer = prev;
8293 								endOffset = idx;
8294 							}
8295 						}
8296 					}
8297 				}
8298 			};
8299 
8300 			function addBogus(node) {
8301 				// Adds a bogus BR element for empty block elements
8302 				if (dom.isBlock(node) && !node.innerHTML && !isIE)
8303 					node.innerHTML = '<br data-mce-bogus="1" />';
8304 
8305 				return node;
8306 			};
8307 
8308 			if (bookmark) {
8309 				if (bookmark.start) {
8310 					rng = dom.createRng();
8311 					root = dom.getRoot();
8312 
8313 					if (t.tridentSel)
8314 						return t.tridentSel.moveToBookmark(bookmark);
8315 
8316 					if (setEndPoint(true) && setEndPoint()) {
8317 						t.setRng(rng);
8318 					}
8319 				} else if (bookmark.id) {
8320 					// Restore start/end points
8321 					restoreEndPoint('start');
8322 					restoreEndPoint('end');
8323 
8324 					if (startContainer) {
8325 						rng = dom.createRng();
8326 						rng.setStart(addBogus(startContainer), startOffset);
8327 						rng.setEnd(addBogus(endContainer), endOffset);
8328 						t.setRng(rng);
8329 					}
8330 				} else if (bookmark.name) {
8331 					t.select(dom.select(bookmark.name)[bookmark.index]);
8332 				} else if (bookmark.rng)
8333 					t.setRng(bookmark.rng);
8334 			}
8335 		},
8336 
8337 		select : function(node, content) {
8338 			var t = this, dom = t.dom, rng = dom.createRng(), idx;
8339 
8340 			function setPoint(node, start) {
8341 				var walker = new TreeWalker(node, node);
8342 
8343 				do {
8344 					// Text node
8345 					if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length !== 0) {
8346 						if (start)
8347 							rng.setStart(node, 0);
8348 						else
8349 							rng.setEnd(node, node.nodeValue.length);
8350 
8351 						return;
8352 					}
8353 
8354 					// BR element
8355 					if (node.nodeName == 'BR') {
8356 						if (start)
8357 							rng.setStartBefore(node);
8358 						else
8359 							rng.setEndBefore(node);
8360 
8361 						return;
8362 					}
8363 				} while (node = (start ? walker.next() : walker.prev()));
8364 			};
8365 
8366 			if (node) {
8367 				idx = dom.nodeIndex(node);
8368 				rng.setStart(node.parentNode, idx);
8369 				rng.setEnd(node.parentNode, idx + 1);
8370 
8371 				// Find first/last text node or BR element
8372 				if (content) {
8373 					setPoint(node, 1);
8374 					setPoint(node);
8375 				}
8376 
8377 				t.setRng(rng);
8378 			}
8379 
8380 			return node;
8381 		},
8382 
8383 		isCollapsed : function() {
8384 			var t = this, r = t.getRng(), s = t.getSel();
8385 
8386 			if (!r || r.item)
8387 				return false;
8388 
8389 			if (r.compareEndPoints)
8390 				return r.compareEndPoints('StartToEnd', r) === 0;
8391 
8392 			return !s || r.collapsed;
8393 		},
8394 
8395 		collapse : function(to_start) {
8396 			var self = this, rng = self.getRng(), node;
8397 
8398 			// Control range on IE
8399 			if (rng.item) {
8400 				node = rng.item(0);
8401 				rng = self.win.document.body.createTextRange();
8402 				rng.moveToElementText(node);
8403 			}
8404 
8405 			rng.collapse(!!to_start);
8406 			self.setRng(rng);
8407 		},
8408 
8409 		getSel : function() {
8410 			var t = this, w = this.win;
8411 
8412 			return w.getSelection ? w.getSelection() : w.document.selection;
8413 		},
8414 
8415 		getRng : function(w3c) {
8416 			var self = this, selection, rng, elm, doc = self.win.document;
8417 
8418 			// Found tridentSel object then we need to use that one
8419 			if (w3c && self.tridentSel) {
8420 				return self.tridentSel.getRangeAt(0);
8421 			}
8422 
8423 			try {
8424 				if (selection = self.getSel()) {
8425 					rng = selection.rangeCount > 0 ? selection.getRangeAt(0) : (selection.createRange ? selection.createRange() : doc.createRange());
8426 				}
8427 			} catch (ex) {
8428 				// IE throws unspecified error here if TinyMCE is placed in a frame/iframe
8429 			}
8430 
8431 			// We have W3C ranges and it's IE then fake control selection since IE9 doesn't handle that correctly yet
8432 			if (tinymce.isIE && rng && rng.setStart && doc.selection.createRange().item) {
8433 				elm = doc.selection.createRange().item(0);
8434 				rng = doc.createRange();
8435 				rng.setStartBefore(elm);
8436 				rng.setEndAfter(elm);
8437 			}
8438 
8439 			// No range found then create an empty one
8440 			// This can occur when the editor is placed in a hidden container element on Gecko
8441 			// Or on IE when there was an exception
8442 			if (!rng) {
8443 				rng = doc.createRange ? doc.createRange() : doc.body.createTextRange();
8444 			}
8445 
8446 			// If range is at start of document then move it to start of body
8447 			if (rng.setStart && rng.startContainer.nodeType === 9 && rng.collapsed) {
8448 				elm = self.dom.getRoot();
8449 				rng.setStart(elm, 0);
8450 				rng.setEnd(elm, 0);
8451 			}
8452 
8453 			if (self.selectedRange && self.explicitRange) {
8454 				if (rng.compareBoundaryPoints(rng.START_TO_START, self.selectedRange) === 0 && rng.compareBoundaryPoints(rng.END_TO_END, self.selectedRange) === 0) {
8455 					// Safari, Opera and Chrome only ever select text which causes the range to change.
8456 					// This lets us use the originally set range if the selection hasn't been changed by the user.
8457 					rng = self.explicitRange;
8458 				} else {
8459 					self.selectedRange = null;
8460 					self.explicitRange = null;
8461 				}
8462 			}
8463 
8464 			return rng;
8465 		},
8466 
8467 		setRng : function(r, forward) {
8468 			var s, t = this;
8469 
8470 			if (!t.tridentSel) {
8471 				s = t.getSel();
8472 
8473 				if (s) {
8474 					t.explicitRange = r;
8475 
8476 					try {
8477 						s.removeAllRanges();
8478 					} catch (ex) {
8479 						// IE9 might throw errors here don't know why
8480 					}
8481 
8482 					s.addRange(r);
8483 
8484 					// Forward is set to false and we have an extend function
8485 					if (forward === false && s.extend) {
8486 						s.collapse(r.endContainer, r.endOffset);
8487 						s.extend(r.startContainer, r.startOffset);
8488 					}
8489 
8490 					// adding range isn't always successful so we need to check range count otherwise an exception can occur
8491 					t.selectedRange = s.rangeCount > 0 ? s.getRangeAt(0) : null;
8492 				}
8493 			} else {
8494 				// Is W3C Range
8495 				if (r.cloneRange) {
8496 					try {
8497 						t.tridentSel.addRange(r);
8498 						return;
8499 					} catch (ex) {
8500 						//IE9 throws an error here if called before selection is placed in the editor
8501 					}
8502 				}
8503 
8504 				// Is IE specific range
8505 				try {
8506 					r.select();
8507 				} catch (ex) {
8508 					// Needed for some odd IE bug #1843306
8509 				}
8510 			}
8511 		},
8512 
8513 		setNode : function(n) {
8514 			var t = this;
8515 
8516 			t.setContent(t.dom.getOuterHTML(n));
8517 
8518 			return n;
8519 		},
8520 
8521 		getNode : function() {
8522 			var t = this, rng = t.getRng(), sel = t.getSel(), elm, start = rng.startContainer, end = rng.endContainer;
8523 
8524 			function skipEmptyTextNodes(n, forwards) {
8525 				var orig = n;
8526 				while (n && n.nodeType === 3 && n.length === 0) {
8527 					n = forwards ? n.nextSibling : n.previousSibling;
8528 				}
8529 				return n || orig;
8530 			};
8531 
8532 			// Range maybe lost after the editor is made visible again
8533 			if (!rng)
8534 				return t.dom.getRoot();
8535 
8536 			if (rng.setStart) {
8537 				elm = rng.commonAncestorContainer;
8538 
8539 				// Handle selection a image or other control like element such as anchors
8540 				if (!rng.collapsed) {
8541 					if (rng.startContainer == rng.endContainer) {
8542 						if (rng.endOffset - rng.startOffset < 2) {
8543 							if (rng.startContainer.hasChildNodes())
8544 								elm = rng.startContainer.childNodes[rng.startOffset];
8545 						}
8546 					}
8547 
8548 					// If the anchor node is a element instead of a text node then return this element
8549 					//if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1)
8550 					//	return sel.anchorNode.childNodes[sel.anchorOffset];
8551 
8552 					// Handle cases where the selection is immediately wrapped around a node and return that node instead of it's parent.
8553 					// This happens when you double click an underlined word in FireFox.
8554 					if (start.nodeType === 3 && end.nodeType === 3) {
8555 						if (start.length === rng.startOffset) {
8556 							start = skipEmptyTextNodes(start.nextSibling, true);
8557 						} else {
8558 							start = start.parentNode;
8559 						}
8560 						if (rng.endOffset === 0) {
8561 							end = skipEmptyTextNodes(end.previousSibling, false);
8562 						} else {
8563 							end = end.parentNode;
8564 						}
8565 
8566 						if (start && start === end)
8567 							return start;
8568 					}
8569 				}
8570 
8571 				if (elm && elm.nodeType == 3)
8572 					return elm.parentNode;
8573 
8574 				return elm;
8575 			}
8576 
8577 			return rng.item ? rng.item(0) : rng.parentElement();
8578 		},
8579 
8580 		getSelectedBlocks : function(st, en) {
8581 			var t = this, dom = t.dom, sb, eb, n, bl = [];
8582 
8583 			sb = dom.getParent(st || t.getStart(), dom.isBlock);
8584 			eb = dom.getParent(en || t.getEnd(), dom.isBlock);
8585 
8586 			if (sb)
8587 				bl.push(sb);
8588 
8589 			if (sb && eb && sb != eb) {
8590 				n = sb;
8591 
8592 				var walker = new TreeWalker(sb, dom.getRoot());
8593 				while ((n = walker.next()) && n != eb) {
8594 					if (dom.isBlock(n))
8595 						bl.push(n);
8596 				}
8597 			}
8598 
8599 			if (eb && sb != eb)
8600 				bl.push(eb);
8601 
8602 			return bl;
8603 		},
8604 
8605 		isForward: function(){
8606 			var dom = this.dom, sel = this.getSel(), anchorRange, focusRange;
8607 
8608 			// No support for selection direction then always return true
8609 			if (!sel || sel.anchorNode == null || sel.focusNode == null) {
8610 				return true;
8611 			}
8612 
8613 			anchorRange = dom.createRng();
8614 			anchorRange.setStart(sel.anchorNode, sel.anchorOffset);
8615 			anchorRange.collapse(true);
8616 
8617 			focusRange = dom.createRng();
8618 			focusRange.setStart(sel.focusNode, sel.focusOffset);
8619 			focusRange.collapse(true);
8620 
8621 			return anchorRange.compareBoundaryPoints(anchorRange.START_TO_START, focusRange) <= 0;
8622 		},
8623 
8624 		normalize : function() {
8625 			var self = this, rng, normalized, collapsed, node, sibling;
8626 
8627 			function normalizeEndPoint(start) {
8628 				var container, offset, walker, dom = self.dom, body = dom.getRoot(), node, nonEmptyElementsMap, nodeName;
8629 
8630 				function hasBrBeforeAfter(node, left) {
8631 					var walker = new TreeWalker(node, dom.getParent(node.parentNode, dom.isBlock) || body);
8632 
8633 					while (node = walker[left ? 'prev' : 'next']()) {
8634 						if (node.nodeName === "BR") {
8635 							return true;
8636 						}
8637 					}
8638 				};
8639 
8640 				// Walks the dom left/right to find a suitable text node to move the endpoint into
8641 				// It will only walk within the current parent block or body and will stop if it hits a block or a BR/IMG
8642 				function findTextNodeRelative(left, startNode) {
8643 					var walker, lastInlineElement;
8644 
8645 					startNode = startNode || container;
8646 					walker = new TreeWalker(startNode, dom.getParent(startNode.parentNode, dom.isBlock) || body);
8647 
8648 					// Walk left until we hit a text node we can move to or a block/br/img
8649 					while (node = walker[left ? 'prev' : 'next']()) {
8650 						// Found text node that has a length
8651 						if (node.nodeType === 3 && node.nodeValue.length > 0) {
8652 							container = node;
8653 							offset = left ? node.nodeValue.length : 0;
8654 							normalized = true;
8655 							return;
8656 						}
8657 
8658 						// Break if we find a block or a BR/IMG/INPUT etc
8659 						if (dom.isBlock(node) || nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
8660 							return;
8661 						}
8662 
8663 						lastInlineElement = node;
8664 					}
8665 
8666 					// Only fetch the last inline element when in caret mode for now
8667 					if (collapsed && lastInlineElement) {
8668 						container = lastInlineElement;
8669 						normalized = true;
8670 						offset = 0;
8671 					}
8672 				};
8673 
8674 				container = rng[(start ? 'start' : 'end') + 'Container'];
8675 				offset = rng[(start ? 'start' : 'end') + 'Offset'];
8676 				nonEmptyElementsMap = dom.schema.getNonEmptyElements();
8677 
8678 				// If the container is a document move it to the body element
8679 				if (container.nodeType === 9) {
8680 					container = dom.getRoot();
8681 					offset = 0;
8682 				}
8683 
8684 				// If the container is body try move it into the closest text node or position
8685 				if (container === body) {
8686 					// If start is before/after a image, table etc
8687 					if (start) {
8688 						node = container.childNodes[offset > 0 ? offset - 1 : 0];
8689 						if (node) {
8690 							nodeName = node.nodeName.toLowerCase();
8691 							if (nonEmptyElementsMap[node.nodeName] || node.nodeName == "TABLE") {
8692 								return;
8693 							}
8694 						}
8695 					}
8696 
8697 					// Resolve the index
8698 					if (container.hasChildNodes()) {
8699 						container = container.childNodes[Math.min(!start && offset > 0 ? offset - 1 : offset, container.childNodes.length - 1)];
8700 						offset = 0;
8701 
8702 						// Don't walk into elements that doesn't have any child nodes like a IMG
8703 						if (container.hasChildNodes() && !/TABLE/.test(container.nodeName)) {
8704 							// Walk the DOM to find a text node to place the caret at or a BR
8705 							node = container;
8706 							walker = new TreeWalker(container, body);
8707 
8708 							do {
8709 								// Found a text node use that position
8710 								if (node.nodeType === 3 && node.nodeValue.length > 0) {
8711 									offset = start ? 0 : node.nodeValue.length;
8712 									container = node;
8713 									normalized = true;
8714 									break;
8715 								}
8716 
8717 								// Found a BR/IMG element that we can place the caret before
8718 								if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
8719 									offset = dom.nodeIndex(node);
8720 									container = node.parentNode;
8721 
8722 									// Put caret after image when moving the end point
8723 									if (node.nodeName ==  "IMG" && !start) {
8724 										offset++;
8725 									}
8726 
8727 									normalized = true;
8728 									break;
8729 								}
8730 							} while (node = (start ? walker.next() : walker.prev()));
8731 						}
8732 					}
8733 				}
8734 
8735 				// Lean the caret to the left if possible
8736 				if (collapsed) {
8737 					// So this: <b>x</b><i>|x</i>
8738 					// Becomes: <b>x|</b><i>x</i>
8739 					// Seems that only gecko has issues with this
8740 					if (container.nodeType === 3 && offset === 0) {
8741 						findTextNodeRelative(true);
8742 					}
8743 
8744 					// Lean left into empty inline elements when the caret is before a BR
8745 					// So this: <i><b></b><i>|<br></i>
8746 					// Becomes: <i><b>|</b><i><br></i>
8747 					// Seems that only gecko has issues with this
8748 					if (container.nodeType === 1) {
8749 						node = container.childNodes[offset];
8750 						if(node && node.nodeName === 'BR' && !hasBrBeforeAfter(node) && !hasBrBeforeAfter(node, true)) {
8751 							findTextNodeRelative(true, container.childNodes[offset]);
8752 						}
8753 					}
8754 				}
8755 
8756 				// Lean the start of the selection right if possible
8757 				// So this: x[<b>x]</b>
8758 				// Becomes: x<b>[x]</b>
8759 				if (start && !collapsed && container.nodeType === 3 && offset === container.nodeValue.length) {
8760 					findTextNodeRelative(false);
8761 				}
8762 
8763 				// Set endpoint if it was normalized
8764 				if (normalized)
8765 					rng['set' + (start ? 'Start' : 'End')](container, offset);
8766 			};
8767 
8768 			// Normalize only on non IE browsers for now
8769 			if (tinymce.isIE)
8770 				return;
8771 			
8772 			rng = self.getRng();
8773 			collapsed = rng.collapsed;
8774 
8775 			// Normalize the end points
8776 			normalizeEndPoint(true);
8777 
8778 			if (!collapsed)
8779 				normalizeEndPoint();
8780 
8781 			// Set the selection if it was normalized
8782 			if (normalized) {
8783 				// If it was collapsed then make sure it still is
8784 				if (collapsed) {
8785 					rng.collapse(true);
8786 				}
8787 
8788 				//console.log(self.dom.dumpRng(rng));
8789 				self.setRng(rng, self.isForward());
8790 			}
8791 		},
8792 
8793 		selectorChanged: function(selector, callback) {
8794 			var self = this, currentSelectors;
8795 
8796 			if (!self.selectorChangedData) {
8797 				self.selectorChangedData = {};
8798 				currentSelectors = {};
8799 
8800 				self.editor.onNodeChange.addToTop(function(ed, cm, node) {
8801 					var dom = self.dom, parents = dom.getParents(node, null, dom.getRoot()), matchedSelectors = {};
8802 
8803 					// Check for new matching selectors
8804 					each(self.selectorChangedData, function(callbacks, selector) {
8805 						each(parents, function(node) {
8806 							if (dom.is(node, selector)) {
8807 								if (!currentSelectors[selector]) {
8808 									// Execute callbacks
8809 									each(callbacks, function(callback) {
8810 										callback(true, {node: node, selector: selector, parents: parents});
8811 									});
8812 
8813 									currentSelectors[selector] = callbacks;
8814 								}
8815 
8816 								matchedSelectors[selector] = callbacks;
8817 								return false;
8818 							}
8819 						});
8820 					});
8821 
8822 					// Check if current selectors still match
8823 					each(currentSelectors, function(callbacks, selector) {
8824 						if (!matchedSelectors[selector]) {
8825 							delete currentSelectors[selector];
8826 
8827 							each(callbacks, function(callback) {
8828 								callback(false, {node: node, selector: selector, parents: parents});
8829 							});
8830 						}
8831 					});
8832 				});
8833 			}
8834 
8835 			// Add selector listeners
8836 			if (!self.selectorChangedData[selector]) {
8837 				self.selectorChangedData[selector] = [];
8838 			}
8839 
8840 			self.selectorChangedData[selector].push(callback);
8841 
8842 			return self;
8843 		},
8844 
8845 		destroy : function(manual) {
8846 			var self = this;
8847 
8848 			self.win = null;
8849 
8850 			// Manual destroy then remove unload handler
8851 			if (!manual)
8852 				tinymce.removeUnload(self.destroy);
8853 		},
8854 
8855 		// IE has an issue where you can't select/move the caret by clicking outside the body if the document is in standards mode
8856 		_fixIESelection : function() {
8857 			var dom = this.dom, doc = dom.doc, body = doc.body, started, startRng, htmlElm;
8858 
8859 			// Return range from point or null if it failed
8860 			function rngFromPoint(x, y) {
8861 				var rng = body.createTextRange();
8862 
8863 				try {
8864 					rng.moveToPoint(x, y);
8865 				} catch (ex) {
8866 					// IE sometimes throws and exception, so lets just ignore it
8867 					rng = null;
8868 				}
8869 
8870 				return rng;
8871 			};
8872 
8873 			// Fires while the selection is changing
8874 			function selectionChange(e) {
8875 				var pointRng;
8876 
8877 				// Check if the button is down or not
8878 				if (e.button) {
8879 					// Create range from mouse position
8880 					pointRng = rngFromPoint(e.x, e.y);
8881 
8882 					if (pointRng) {
8883 						// Check if pointRange is before/after selection then change the endPoint
8884 						if (pointRng.compareEndPoints('StartToStart', startRng) > 0)
8885 							pointRng.setEndPoint('StartToStart', startRng);
8886 						else
8887 							pointRng.setEndPoint('EndToEnd', startRng);
8888 
8889 						pointRng.select();
8890 					}
8891 				} else
8892 					endSelection();
8893 			}
8894 
8895 			// Removes listeners
8896 			function endSelection() {
8897 				var rng = doc.selection.createRange();
8898 
8899 				// If the range is collapsed then use the last start range
8900 				if (startRng && !rng.item && rng.compareEndPoints('StartToEnd', rng) === 0)
8901 					startRng.select();
8902 
8903 				dom.unbind(doc, 'mouseup', endSelection);
8904 				dom.unbind(doc, 'mousemove', selectionChange);
8905 				startRng = started = 0;
8906 			};
8907 
8908 			// Make HTML element unselectable since we are going to handle selection by hand
8909 			doc.documentElement.unselectable = true;
8910 			
8911 			// Detect when user selects outside BODY
8912 			dom.bind(doc, ['mousedown', 'contextmenu'], function(e) {
8913 				if (e.target.nodeName === 'HTML') {
8914 					if (started)
8915 						endSelection();
8916 
8917 					// Detect vertical scrollbar, since IE will fire a mousedown on the scrollbar and have target set as HTML
8918 					htmlElm = doc.documentElement;
8919 					if (htmlElm.scrollHeight > htmlElm.clientHeight)
8920 						return;
8921 
8922 					started = 1;
8923 					// Setup start position
8924 					startRng = rngFromPoint(e.x, e.y);
8925 					if (startRng) {
8926 						// Listen for selection change events
8927 						dom.bind(doc, 'mouseup', endSelection);
8928 						dom.bind(doc, 'mousemove', selectionChange);
8929 
8930 						dom.win.focus();
8931 						startRng.select();
8932 					}
8933 				}
8934 			});
8935 		}
8936 	});
8937 })(tinymce);
8938 
8939 (function(tinymce) {
8940 	tinymce.dom.Serializer = function(settings, dom, schema) {
8941 		var onPreProcess, onPostProcess, isIE = tinymce.isIE, each = tinymce.each, htmlParser;
8942 
8943 		// Support the old apply_source_formatting option
8944 		if (!settings.apply_source_formatting)
8945 			settings.indent = false;
8946 
8947 		// Default DOM and Schema if they are undefined
8948 		dom = dom || tinymce.DOM;
8949 		schema = schema || new tinymce.html.Schema(settings);
8950 		settings.entity_encoding = settings.entity_encoding || 'named';
8951 		settings.remove_trailing_brs = "remove_trailing_brs" in settings ? settings.remove_trailing_brs : true;
8952 
8953 		onPreProcess = new tinymce.util.Dispatcher(self);
8954 
8955 		onPostProcess = new tinymce.util.Dispatcher(self);
8956 
8957 		htmlParser = new tinymce.html.DomParser(settings, schema);
8958 
8959 		// Convert move data-mce-src, data-mce-href and data-mce-style into nodes or process them if needed
8960 		htmlParser.addAttributeFilter('src,href,style', function(nodes, name) {
8961 			var i = nodes.length, node, value, internalName = 'data-mce-' + name, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope, undef;
8962 
8963 			while (i--) {
8964 				node = nodes[i];
8965 
8966 				value = node.attributes.map[internalName];
8967 				if (value !== undef) {
8968 					// Set external name to internal value and remove internal
8969 					node.attr(name, value.length > 0 ? value : null);
8970 					node.attr(internalName, null);
8971 				} else {
8972 					// No internal attribute found then convert the value we have in the DOM
8973 					value = node.attributes.map[name];
8974 
8975 					if (name === "style")
8976 						value = dom.serializeStyle(dom.parseStyle(value), node.name);
8977 					else if (urlConverter)
8978 						value = urlConverter.call(urlConverterScope, value, name, node.name);
8979 
8980 					node.attr(name, value.length > 0 ? value : null);
8981 				}
8982 			}
8983 		});
8984 
8985 		// Remove internal classes mceItem<..> or mceSelected
8986 		htmlParser.addAttributeFilter('class', function(nodes, name) {
8987 			var i = nodes.length, node, value;
8988 
8989 			while (i--) {
8990 				node = nodes[i];
8991 				value = node.attr('class').replace(/(?:^|\s)mce(Item\w+|Selected)(?!\S)/g, '');
8992 				node.attr('class', value.length > 0 ? value : null);
8993 			}
8994 		});
8995 
8996 		// Remove bookmark elements
8997 		htmlParser.addAttributeFilter('data-mce-type', function(nodes, name, args) {
8998 			var i = nodes.length, node;
8999 
9000 			while (i--) {
9001 				node = nodes[i];
9002 
9003 				if (node.attributes.map['data-mce-type'] === 'bookmark' && !args.cleanup)
9004 					node.remove();
9005 			}
9006 		});
9007 
9008 		// Remove expando attributes
9009 		htmlParser.addAttributeFilter('data-mce-expando', function(nodes, name, args) {
9010 			var i = nodes.length;
9011 
9012 			while (i--) {
9013 				nodes[i].attr(name, null);
9014 			}
9015 		});
9016 
9017 		// Force script into CDATA sections and remove the mce- prefix also add comments around styles
9018 		htmlParser.addNodeFilter('script,style', function(nodes, name) {
9019 			var i = nodes.length, node, value;
9020 
9021 			function trim(value) {
9022 				return value.replace(/(<!--\[CDATA\[|\]\]-->)/g, '\n')
9023 						.replace(/^[\r\n]*|[\r\n]*$/g, '')
9024 						.replace(/^\s*((<!--)?(\s*\/\/)?\s*<!\[CDATA\[|(<!--\s*)?\/\*\s*<!\[CDATA\[\s*\*\/|(\/\/)?\s*<!--|\/\*\s*<!--\s*\*\/)\s*[\r\n]*/gi, '')
9025 						.replace(/\s*(\/\*\s*\]\]>\s*\*\/(-->)?|\s*\/\/\s*\]\]>(-->)?|\/\/\s*(-->)?|\]\]>|\/\*\s*-->\s*\*\/|\s*-->\s*)\s*$/g, '');
9026 			};
9027 
9028 			while (i--) {
9029 				node = nodes[i];
9030 				value = node.firstChild ? node.firstChild.value : '';
9031 
9032 				if (name === "script") {
9033 					// Remove mce- prefix from script elements
9034 					node.attr('type', (node.attr('type') || 'text/javascript').replace(/^mce\-/, ''));
9035 
9036 					if (value.length > 0)
9037 						node.firstChild.value = '// <![CDATA[\n' + trim(value) + '\n// ]]>';
9038 				} else {
9039 					if (value.length > 0)
9040 						node.firstChild.value = '<!--\n' + trim(value) + '\n-->';
9041 				}
9042 			}
9043 		});
9044 
9045 		// Convert comments to cdata and handle protected comments
9046 		htmlParser.addNodeFilter('#comment', function(nodes, name) {
9047 			var i = nodes.length, node;
9048 
9049 			while (i--) {
9050 				node = nodes[i];
9051 
9052 				if (node.value.indexOf('[CDATA[') === 0) {
9053 					node.name = '#cdata';
9054 					node.type = 4;
9055 					node.value = node.value.replace(/^\[CDATA\[|\]\]$/g, '');
9056 				} else if (node.value.indexOf('mce:protected ') === 0) {
9057 					node.name = "#text";
9058 					node.type = 3;
9059 					node.raw = true;
9060 					node.value = unescape(node.value).substr(14);
9061 				}
9062 			}
9063 		});
9064 
9065 		htmlParser.addNodeFilter('xml:namespace,input', function(nodes, name) {
9066 			var i = nodes.length, node;
9067 
9068 			while (i--) {
9069 				node = nodes[i];
9070 				if (node.type === 7)
9071 					node.remove();
9072 				else if (node.type === 1) {
9073 					if (name === "input" && !("type" in node.attributes.map))
9074 						node.attr('type', 'text');
9075 				}
9076 			}
9077 		});
9078 
9079 		// Fix list elements, TODO: Replace this later
9080 		if (settings.fix_list_elements) {
9081 			htmlParser.addNodeFilter('ul,ol', function(nodes, name) {
9082 				var i = nodes.length, node, parentNode;
9083 
9084 				while (i--) {
9085 					node = nodes[i];
9086 					parentNode = node.parent;
9087 
9088 					if (parentNode.name === 'ul' || parentNode.name === 'ol') {
9089 						if (node.prev && node.prev.name === 'li') {
9090 							node.prev.append(node);
9091 						}
9092 					}
9093 				}
9094 			});
9095 		}
9096 
9097 		// Remove internal data attributes
9098 		htmlParser.addAttributeFilter('data-mce-src,data-mce-href,data-mce-style', function(nodes, name) {
9099 			var i = nodes.length;
9100 
9101 			while (i--) {
9102 				nodes[i].attr(name, null);
9103 			}
9104 		});
9105 
9106 		// Return public methods
9107 		return {
9108 			schema : schema,
9109 
9110 			addNodeFilter : htmlParser.addNodeFilter,
9111 
9112 			addAttributeFilter : htmlParser.addAttributeFilter,
9113 
9114 			onPreProcess : onPreProcess,
9115 
9116 			onPostProcess : onPostProcess,
9117 
9118 			serialize : function(node, args) {
9119 				var impl, doc, oldDoc, htmlSerializer, content;
9120 
9121 				// Explorer won't clone contents of script and style and the
9122 				// selected index of select elements are cleared on a clone operation.
9123 				if (isIE && dom.select('script,style,select,map').length > 0) {
9124 					content = node.innerHTML;
9125 					node = node.cloneNode(false);
9126 					dom.setHTML(node, content);
9127 				} else
9128 					node = node.cloneNode(true);
9129 
9130 				// Nodes needs to be attached to something in WebKit/Opera
9131 				// Older builds of Opera crashes if you attach the node to an document created dynamically
9132 				// and since we can't feature detect a crash we need to sniff the acutal build number
9133 				// This fix will make DOM ranges and make Sizzle happy!
9134 				impl = node.ownerDocument.implementation;
9135 				if (impl.createHTMLDocument) {
9136 					// Create an empty HTML document
9137 					doc = impl.createHTMLDocument("");
9138 
9139 					// Add the element or it's children if it's a body element to the new document
9140 					each(node.nodeName == 'BODY' ? node.childNodes : [node], function(node) {
9141 						doc.body.appendChild(doc.importNode(node, true));
9142 					});
9143 
9144 					// Grab first child or body element for serialization
9145 					if (node.nodeName != 'BODY')
9146 						node = doc.body.firstChild;
9147 					else
9148 						node = doc.body;
9149 
9150 					// set the new document in DOMUtils so createElement etc works
9151 					oldDoc = dom.doc;
9152 					dom.doc = doc;
9153 				}
9154 
9155 				args = args || {};
9156 				args.format = args.format || 'html';
9157 
9158 				// Pre process
9159 				if (!args.no_events) {
9160 					args.node = node;
9161 					onPreProcess.dispatch(self, args);
9162 				}
9163 
9164 				// Setup serializer
9165 				htmlSerializer = new tinymce.html.Serializer(settings, schema);
9166 
9167 				// Parse and serialize HTML
9168 				args.content = htmlSerializer.serialize(
9169 					htmlParser.parse(tinymce.trim(args.getInner ? node.innerHTML : dom.getOuterHTML(node)), args)
9170 				);
9171 
9172 				// Replace all BOM characters for now until we can find a better solution
9173 				if (!args.cleanup)
9174 					args.content = args.content.replace(/\uFEFF|\u200B/g, '');
9175 
9176 				// Post process
9177 				if (!args.no_events)
9178 					onPostProcess.dispatch(self, args);
9179 
9180 				// Restore the old document if it was changed
9181 				if (oldDoc)
9182 					dom.doc = oldDoc;
9183 
9184 				args.node = null;
9185 
9186 				return args.content;
9187 			},
9188 
9189 			addRules : function(rules) {
9190 				schema.addValidElements(rules);
9191 			},
9192 
9193 			setRules : function(rules) {
9194 				schema.setValidElements(rules);
9195 			}
9196 		};
9197 	};
9198 })(tinymce);
9199 (function(tinymce) {
9200 	tinymce.dom.ScriptLoader = function(settings) {
9201 		var QUEUED = 0,
9202 			LOADING = 1,
9203 			LOADED = 2,
9204 			states = {},
9205 			queue = [],
9206 			scriptLoadedCallbacks = {},
9207 			queueLoadedCallbacks = [],
9208 			loading = 0,
9209 			undef;
9210 
9211 		function loadScript(url, callback) {
9212 			var t = this, dom = tinymce.DOM, elm, uri, loc, id;
9213 
9214 			// Execute callback when script is loaded
9215 			function done() {
9216 				dom.remove(id);
9217 
9218 				if (elm)
9219 					elm.onreadystatechange = elm.onload = elm = null;
9220 
9221 				callback();
9222 			};
9223 			
9224 			function error() {
9225 				// Report the error so it's easier for people to spot loading errors
9226 				if (typeof(console) !== "undefined" && console.log)
9227 					console.log("Failed to load: " + url);
9228 
9229 				// We can't mark it as done if there is a load error since
9230 				// A) We don't want to produce 404 errors on the server and
9231 				// B) the onerror event won't fire on all browsers.
9232 				// done();
9233 			};
9234 
9235 			id = dom.uniqueId();
9236 
9237 			if (tinymce.isIE6) {
9238 				uri = new tinymce.util.URI(url);
9239 				loc = location;
9240 
9241 				// If script is from same domain and we
9242 				// use IE 6 then use XHR since it's more reliable
9243 				if (uri.host == loc.hostname && uri.port == loc.port && (uri.protocol + ':') == loc.protocol && uri.protocol.toLowerCase() != 'file') {
9244 					tinymce.util.XHR.send({
9245 						url : tinymce._addVer(uri.getURI()),
9246 						success : function(content) {
9247 							// Create new temp script element
9248 							var script = dom.create('script', {
9249 								type : 'text/javascript'
9250 							});
9251 
9252 							// Evaluate script in global scope
9253 							script.text = content;
9254 							document.getElementsByTagName('head')[0].appendChild(script);
9255 							dom.remove(script);
9256 
9257 							done();
9258 						},
9259 						
9260 						error : error
9261 					});
9262 
9263 					return;
9264 				}
9265 			}
9266 
9267 			// Create new script element
9268 			elm = document.createElement('script');
9269 			elm.id = id;
9270 			elm.type = 'text/javascript';
9271 			elm.src = tinymce._addVer(url);
9272 
9273 			// Add onload listener for non IE browsers since IE9
9274 			// fires onload event before the script is parsed and executed
9275 			if (!tinymce.isIE)
9276 				elm.onload = done;
9277 
9278 			// Add onerror event will get fired on some browsers but not all of them
9279 			elm.onerror = error;
9280 
9281 			// Opera 9.60 doesn't seem to fire the onreadystate event at correctly
9282 			if (!tinymce.isOpera) {
9283 				elm.onreadystatechange = function() {
9284 					var state = elm.readyState;
9285 
9286 					// Loaded state is passed on IE 6 however there
9287 					// are known issues with this method but we can't use
9288 					// XHR in a cross domain loading
9289 					if (state == 'complete' || state == 'loaded')
9290 						done();
9291 				};
9292 			}
9293 
9294 			// Most browsers support this feature so we report errors
9295 			// for those at least to help users track their missing plugins etc
9296 			// todo: Removed since it produced error if the document is unloaded by navigating away, re-add it as an option
9297 			/*elm.onerror = function() {
9298 				alert('Failed to load: ' + url);
9299 			};*/
9300 
9301 			// Add script to document
9302 			(document.getElementsByTagName('head')[0] || document.body).appendChild(elm);
9303 		};
9304 
9305 		this.isDone = function(url) {
9306 			return states[url] == LOADED;
9307 		};
9308 
9309 		this.markDone = function(url) {
9310 			states[url] = LOADED;
9311 		};
9312 
9313 		this.add = this.load = function(url, callback, scope) {
9314 			var item, state = states[url];
9315 
9316 			// Add url to load queue
9317 			if (state == undef) {
9318 				queue.push(url);
9319 				states[url] = QUEUED;
9320 			}
9321 
9322 			if (callback) {
9323 				// Store away callback for later execution
9324 				if (!scriptLoadedCallbacks[url])
9325 					scriptLoadedCallbacks[url] = [];
9326 
9327 				scriptLoadedCallbacks[url].push({
9328 					func : callback,
9329 					scope : scope || this
9330 				});
9331 			}
9332 		};
9333 
9334 		this.loadQueue = function(callback, scope) {
9335 			this.loadScripts(queue, callback, scope);
9336 		};
9337 
9338 		this.loadScripts = function(scripts, callback, scope) {
9339 			var loadScripts;
9340 
9341 			function execScriptLoadedCallbacks(url) {
9342 				// Execute URL callback functions
9343 				tinymce.each(scriptLoadedCallbacks[url], function(callback) {
9344 					callback.func.call(callback.scope);
9345 				});
9346 
9347 				scriptLoadedCallbacks[url] = undef;
9348 			};
9349 
9350 			queueLoadedCallbacks.push({
9351 				func : callback,
9352 				scope : scope || this
9353 			});
9354 
9355 			loadScripts = function() {
9356 				var loadingScripts = tinymce.grep(scripts);
9357 
9358 				// Current scripts has been handled
9359 				scripts.length = 0;
9360 
9361 				// Load scripts that needs to be loaded
9362 				tinymce.each(loadingScripts, function(url) {
9363 					// Script is already loaded then execute script callbacks directly
9364 					if (states[url] == LOADED) {
9365 						execScriptLoadedCallbacks(url);
9366 						return;
9367 					}
9368 
9369 					// Is script not loading then start loading it
9370 					if (states[url] != LOADING) {
9371 						states[url] = LOADING;
9372 						loading++;
9373 
9374 						loadScript(url, function() {
9375 							states[url] = LOADED;
9376 							loading--;
9377 
9378 							execScriptLoadedCallbacks(url);
9379 
9380 							// Load more scripts if they where added by the recently loaded script
9381 							loadScripts();
9382 						});
9383 					}
9384 				});
9385 
9386 				// No scripts are currently loading then execute all pending queue loaded callbacks
9387 				if (!loading) {
9388 					tinymce.each(queueLoadedCallbacks, function(callback) {
9389 						callback.func.call(callback.scope);
9390 					});
9391 
9392 					queueLoadedCallbacks.length = 0;
9393 				}
9394 			};
9395 
9396 			loadScripts();
9397 		};
9398 	};
9399 
9400 	// Global script loader
9401 	tinymce.ScriptLoader = new tinymce.dom.ScriptLoader();
9402 })(tinymce);
9403 
9404 (function(tinymce) {
9405 	tinymce.dom.RangeUtils = function(dom) {
9406 		var INVISIBLE_CHAR = '\uFEFF';
9407 
9408 		this.walk = function(rng, callback) {
9409 			var startContainer = rng.startContainer,
9410 				startOffset = rng.startOffset,
9411 				endContainer = rng.endContainer,
9412 				endOffset = rng.endOffset,
9413 				ancestor, startPoint,
9414 				endPoint, node, parent, siblings, nodes;
9415 
9416 			// Handle table cell selection the table plugin enables
9417 			// you to fake select table cells and perform formatting actions on them
9418 			nodes = dom.select('td.mceSelected,th.mceSelected');
9419 			if (nodes.length > 0) {
9420 				tinymce.each(nodes, function(node) {
9421 					callback([node]);
9422 				});
9423 
9424 				return;
9425 			}
9426 
9427 			function exclude(nodes) {
9428 				var node;
9429 
9430 				// First node is excluded
9431 				node = nodes[0];
9432 				if (node.nodeType === 3 && node === startContainer && startOffset >= node.nodeValue.length) {
9433 					nodes.splice(0, 1);
9434 				}
9435 
9436 				// Last node is excluded
9437 				node = nodes[nodes.length - 1];
9438 				if (endOffset === 0 && nodes.length > 0 && node === endContainer && node.nodeType === 3) {
9439 					nodes.splice(nodes.length - 1, 1);
9440 				}
9441 
9442 				return nodes;
9443 			};
9444 
9445 			function collectSiblings(node, name, end_node) {
9446 				var siblings = [];
9447 
9448 				for (; node && node != end_node; node = node[name])
9449 					siblings.push(node);
9450 
9451 				return siblings;
9452 			};
9453 
9454 			function findEndPoint(node, root) {
9455 				do {
9456 					if (node.parentNode == root)
9457 						return node;
9458 
9459 					node = node.parentNode;
9460 				} while(node);
9461 			};
9462 
9463 			function walkBoundary(start_node, end_node, next) {
9464 				var siblingName = next ? 'nextSibling' : 'previousSibling';
9465 
9466 				for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) {
9467 					parent = node.parentNode;
9468 					siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName);
9469 
9470 					if (siblings.length) {
9471 						if (!next)
9472 							siblings.reverse();
9473 
9474 						callback(exclude(siblings));
9475 					}
9476 				}
9477 			};
9478 
9479 			// If index based start position then resolve it
9480 			if (startContainer.nodeType == 1 && startContainer.hasChildNodes())
9481 				startContainer = startContainer.childNodes[startOffset];
9482 
9483 			// If index based end position then resolve it
9484 			if (endContainer.nodeType == 1 && endContainer.hasChildNodes())
9485 				endContainer = endContainer.childNodes[Math.min(endOffset - 1, endContainer.childNodes.length - 1)];
9486 
9487 			// Same container
9488 			if (startContainer == endContainer)
9489 				return callback(exclude([startContainer]));
9490 
9491 			// Find common ancestor and end points
9492 			ancestor = dom.findCommonAncestor(startContainer, endContainer);
9493 				
9494 			// Process left side
9495 			for (node = startContainer; node; node = node.parentNode) {
9496 				if (node === endContainer)
9497 					return walkBoundary(startContainer, ancestor, true);
9498 
9499 				if (node === ancestor)
9500 					break;
9501 			}
9502 
9503 			// Process right side
9504 			for (node = endContainer; node; node = node.parentNode) {
9505 				if (node === startContainer)
9506 					return walkBoundary(endContainer, ancestor);
9507 
9508 				if (node === ancestor)
9509 					break;
9510 			}
9511 
9512 			// Find start/end point
9513 			startPoint = findEndPoint(startContainer, ancestor) || startContainer;
9514 			endPoint = findEndPoint(endContainer, ancestor) || endContainer;
9515 
9516 			// Walk left leaf
9517 			walkBoundary(startContainer, startPoint, true);
9518 
9519 			// Walk the middle from start to end point
9520 			siblings = collectSiblings(
9521 				startPoint == startContainer ? startPoint : startPoint.nextSibling,
9522 				'nextSibling',
9523 				endPoint == endContainer ? endPoint.nextSibling : endPoint
9524 			);
9525 
9526 			if (siblings.length)
9527 				callback(exclude(siblings));
9528 
9529 			// Walk right leaf
9530 			walkBoundary(endContainer, endPoint);
9531 		};
9532 
9533 		this.split = function(rng) {
9534 			var startContainer = rng.startContainer,
9535 				startOffset = rng.startOffset,
9536 				endContainer = rng.endContainer,
9537 				endOffset = rng.endOffset;
9538 
9539 			function splitText(node, offset) {
9540 				return node.splitText(offset);
9541 			};
9542 
9543 			// Handle single text node
9544 			if (startContainer == endContainer && startContainer.nodeType == 3) {
9545 				if (startOffset > 0 && startOffset < startContainer.nodeValue.length) {
9546 					endContainer = splitText(startContainer, startOffset);
9547 					startContainer = endContainer.previousSibling;
9548 
9549 					if (endOffset > startOffset) {
9550 						endOffset = endOffset - startOffset;
9551 						startContainer = endContainer = splitText(endContainer, endOffset).previousSibling;
9552 						endOffset = endContainer.nodeValue.length;
9553 						startOffset = 0;
9554 					} else {
9555 						endOffset = 0;
9556 					}
9557 				}
9558 			} else {
9559 				// Split startContainer text node if needed
9560 				if (startContainer.nodeType == 3 && startOffset > 0 && startOffset < startContainer.nodeValue.length) {
9561 					startContainer = splitText(startContainer, startOffset);
9562 					startOffset = 0;
9563 				}
9564 
9565 				// Split endContainer text node if needed
9566 				if (endContainer.nodeType == 3 && endOffset > 0 && endOffset < endContainer.nodeValue.length) {
9567 					endContainer = splitText(endContainer, endOffset).previousSibling;
9568 					endOffset = endContainer.nodeValue.length;
9569 				}
9570 			}
9571 
9572 			return {
9573 				startContainer : startContainer,
9574 				startOffset : startOffset,
9575 				endContainer : endContainer,
9576 				endOffset : endOffset
9577 			};
9578 		};
9579 
9580 	};
9581 
9582 	tinymce.dom.RangeUtils.compareRanges = function(rng1, rng2) {
9583 		if (rng1 && rng2) {
9584 			// Compare native IE ranges
9585 			if (rng1.item || rng1.duplicate) {
9586 				// Both are control ranges and the selected element matches
9587 				if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0))
9588 					return true;
9589 
9590 				// Both are text ranges and the range matches
9591 				if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1))
9592 					return true;
9593 			} else {
9594 				// Compare w3c ranges
9595 				return rng1.startContainer == rng2.startContainer && rng1.startOffset == rng2.startOffset;
9596 			}
9597 		}
9598 
9599 		return false;
9600 	};
9601 })(tinymce);
9602 
9603 (function(tinymce) {
9604 	var Event = tinymce.dom.Event, each = tinymce.each;
9605 
9606 	tinymce.create('tinymce.ui.KeyboardNavigation', {
9607 		KeyboardNavigation: function(settings, dom) {
9608 			var t = this, root = settings.root, items = settings.items,
9609 					enableUpDown = settings.enableUpDown, enableLeftRight = settings.enableLeftRight || !settings.enableUpDown,
9610 					excludeFromTabOrder = settings.excludeFromTabOrder,
9611 					itemFocussed, itemBlurred, rootKeydown, rootFocussed, focussedId;
9612 
9613 			dom = dom || tinymce.DOM;
9614 
9615 			itemFocussed = function(evt) {
9616 				focussedId = evt.target.id;
9617 			};
9618 			
9619 			itemBlurred = function(evt) {
9620 				dom.setAttrib(evt.target.id, 'tabindex', '-1');
9621 			};
9622 			
9623 			rootFocussed = function(evt) {
9624 				var item = dom.get(focussedId);
9625 				dom.setAttrib(item, 'tabindex', '0');
9626 				item.focus();
9627 			};
9628 			
9629 			t.focus = function() {
9630 				dom.get(focussedId).focus();
9631 			};
9632 
9633 			t.destroy = function() {
9634 				each(items, function(item) {
9635 					var elm = dom.get(item.id);
9636 
9637 					dom.unbind(elm, 'focus', itemFocussed);
9638 					dom.unbind(elm, 'blur', itemBlurred);
9639 				});
9640 
9641 				var rootElm = dom.get(root);
9642 				dom.unbind(rootElm, 'focus', rootFocussed);
9643 				dom.unbind(rootElm, 'keydown', rootKeydown);
9644 
9645 				items = dom = root = t.focus = itemFocussed = itemBlurred = rootKeydown = rootFocussed = null;
9646 				t.destroy = function() {};
9647 			};
9648 			
9649 			t.moveFocus = function(dir, evt) {
9650 				var idx = -1, controls = t.controls, newFocus;
9651 
9652 				if (!focussedId)
9653 					return;
9654 
9655 				each(items, function(item, index) {
9656 					if (item.id === focussedId) {
9657 						idx = index;
9658 						return false;
9659 					}
9660 				});
9661 
9662 				idx += dir;
9663 				if (idx < 0) {
9664 					idx = items.length - 1;
9665 				} else if (idx >= items.length) {
9666 					idx = 0;
9667 				}
9668 				
9669 				newFocus = items[idx];
9670 				dom.setAttrib(focussedId, 'tabindex', '-1');
9671 				dom.setAttrib(newFocus.id, 'tabindex', '0');
9672 				dom.get(newFocus.id).focus();
9673 
9674 				if (settings.actOnFocus) {
9675 					settings.onAction(newFocus.id);
9676 				}
9677 
9678 				if (evt)
9679 					Event.cancel(evt);
9680 			};
9681 			
9682 			rootKeydown = function(evt) {
9683 				var DOM_VK_LEFT = 37, DOM_VK_RIGHT = 39, DOM_VK_UP = 38, DOM_VK_DOWN = 40, DOM_VK_ESCAPE = 27, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_SPACE = 32;
9684 				
9685 				switch (evt.keyCode) {
9686 					case DOM_VK_LEFT:
9687 						if (enableLeftRight) t.moveFocus(-1);
9688 						break;
9689 	
9690 					case DOM_VK_RIGHT:
9691 						if (enableLeftRight) t.moveFocus(1);
9692 						break;
9693 	
9694 					case DOM_VK_UP:
9695 						if (enableUpDown) t.moveFocus(-1);
9696 						break;
9697 
9698 					case DOM_VK_DOWN:
9699 						if (enableUpDown) t.moveFocus(1);
9700 						break;
9701 
9702 					case DOM_VK_ESCAPE:
9703 						if (settings.onCancel) {
9704 							settings.onCancel();
9705 							Event.cancel(evt);
9706 						}
9707 						break;
9708 
9709 					case DOM_VK_ENTER:
9710 					case DOM_VK_RETURN:
9711 					case DOM_VK_SPACE:
9712 						if (settings.onAction) {
9713 							settings.onAction(focussedId);
9714 							Event.cancel(evt);
9715 						}
9716 						break;
9717 				}
9718 			};
9719 
9720 			// Set up state and listeners for each item.
9721 			each(items, function(item, idx) {
9722 				var tabindex, elm;
9723 
9724 				if (!item.id) {
9725 					item.id = dom.uniqueId('_mce_item_');
9726 				}
9727 
9728 				elm = dom.get(item.id);
9729 
9730 				if (excludeFromTabOrder) {
9731 					dom.bind(elm, 'blur', itemBlurred);
9732 					tabindex = '-1';
9733 				} else {
9734 					tabindex = (idx === 0 ? '0' : '-1');
9735 				}
9736 
9737 				elm.setAttribute('tabindex', tabindex);
9738 				dom.bind(elm, 'focus', itemFocussed);
9739 			});
9740 			
9741 			// Setup initial state for root element.
9742 			if (items[0]){
9743 				focussedId = items[0].id;
9744 			}
9745 
9746 			dom.setAttrib(root, 'tabindex', '-1');
9747 
9748 			// Setup listeners for root element.
9749 			var rootElm = dom.get(root);
9750 			dom.bind(rootElm, 'focus', rootFocussed);
9751 			dom.bind(rootElm, 'keydown', rootKeydown);
9752 		}
9753 	});
9754 })(tinymce);
9755 
9756 (function(tinymce) {
9757 	// Shorten class names
9758 	var DOM = tinymce.DOM, is = tinymce.is;
9759 
9760 	tinymce.create('tinymce.ui.Control', {
9761 		Control : function(id, s, editor) {
9762 			this.id = id;
9763 			this.settings = s = s || {};
9764 			this.rendered = false;
9765 			this.onRender = new tinymce.util.Dispatcher(this);
9766 			this.classPrefix = '';
9767 			this.scope = s.scope || this;
9768 			this.disabled = 0;
9769 			this.active = 0;
9770 			this.editor = editor;
9771 		},
9772 		
9773 		setAriaProperty : function(property, value) {
9774 			var element = DOM.get(this.id + '_aria') || DOM.get(this.id);
9775 			if (element) {
9776 				DOM.setAttrib(element, 'aria-' + property, !!value);
9777 			}
9778 		},
9779 		
9780 		focus : function() {
9781 			DOM.get(this.id).focus();
9782 		},
9783 
9784 		setDisabled : function(s) {
9785 			if (s != this.disabled) {
9786 				this.setAriaProperty('disabled', s);
9787 
9788 				this.setState('Disabled', s);
9789 				this.setState('Enabled', !s);
9790 				this.disabled = s;
9791 			}
9792 		},
9793 
9794 		isDisabled : function() {
9795 			return this.disabled;
9796 		},
9797 
9798 		setActive : function(s) {
9799 			if (s != this.active) {
9800 				this.setState('Active', s);
9801 				this.active = s;
9802 				this.setAriaProperty('pressed', s);
9803 			}
9804 		},
9805 
9806 		isActive : function() {
9807 			return this.active;
9808 		},
9809 
9810 		setState : function(c, s) {
9811 			var n = DOM.get(this.id);
9812 
9813 			c = this.classPrefix + c;
9814 
9815 			if (s)
9816 				DOM.addClass(n, c);
9817 			else
9818 				DOM.removeClass(n, c);
9819 		},
9820 
9821 		isRendered : function() {
9822 			return this.rendered;
9823 		},
9824 
9825 		renderHTML : function() {
9826 		},
9827 
9828 		renderTo : function(n) {
9829 			DOM.setHTML(n, this.renderHTML());
9830 		},
9831 
9832 		postRender : function() {
9833 			var t = this, b;
9834 
9835 			// Set pending states
9836 			if (is(t.disabled)) {
9837 				b = t.disabled;
9838 				t.disabled = -1;
9839 				t.setDisabled(b);
9840 			}
9841 
9842 			if (is(t.active)) {
9843 				b = t.active;
9844 				t.active = -1;
9845 				t.setActive(b);
9846 			}
9847 		},
9848 
9849 		remove : function() {
9850 			DOM.remove(this.id);
9851 			this.destroy();
9852 		},
9853 
9854 		destroy : function() {
9855 			tinymce.dom.Event.clear(this.id);
9856 		}
9857 	});
9858 })(tinymce);
9859 tinymce.create('tinymce.ui.Container:tinymce.ui.Control', {
9860 	Container : function(id, s, editor) {
9861 		this.parent(id, s, editor);
9862 
9863 		this.controls = [];
9864 
9865 		this.lookup = {};
9866 	},
9867 
9868 	add : function(c) {
9869 		this.lookup[c.id] = c;
9870 		this.controls.push(c);
9871 
9872 		return c;
9873 	},
9874 
9875 	get : function(n) {
9876 		return this.lookup[n];
9877 	}
9878 });
9879 
9880 
9881 tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {
9882 	Separator : function(id, s) {
9883 		this.parent(id, s);
9884 		this.classPrefix = 'mceSeparator';
9885 		this.setDisabled(true);
9886 	},
9887 
9888 	renderHTML : function() {
9889 		return tinymce.DOM.createHTML('span', {'class' : this.classPrefix, role : 'separator', 'aria-orientation' : 'vertical', tabindex : '-1'});
9890 	}
9891 });
9892 
9893 (function(tinymce) {
9894 	var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
9895 
9896 	tinymce.create('tinymce.ui.MenuItem:tinymce.ui.Control', {
9897 		MenuItem : function(id, s) {
9898 			this.parent(id, s);
9899 			this.classPrefix = 'mceMenuItem';
9900 		},
9901 
9902 		setSelected : function(s) {
9903 			this.setState('Selected', s);
9904 			this.setAriaProperty('checked', !!s);
9905 			this.selected = s;
9906 		},
9907 
9908 		isSelected : function() {
9909 			return this.selected;
9910 		},
9911 
9912 		postRender : function() {
9913 			var t = this;
9914 			
9915 			t.parent();
9916 
9917 			// Set pending state
9918 			if (is(t.selected))
9919 				t.setSelected(t.selected);
9920 		}
9921 	});
9922 })(tinymce);
9923 
9924 (function(tinymce) {
9925 	var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
9926 
9927 	tinymce.create('tinymce.ui.Menu:tinymce.ui.MenuItem', {
9928 		Menu : function(id, s) {
9929 			var t = this;
9930 
9931 			t.parent(id, s);
9932 			t.items = {};
9933 			t.collapsed = false;
9934 			t.menuCount = 0;
9935 			t.onAddItem = new tinymce.util.Dispatcher(this);
9936 		},
9937 
9938 		expand : function(d) {
9939 			var t = this;
9940 
9941 			if (d) {
9942 				walk(t, function(o) {
9943 					if (o.expand)
9944 						o.expand();
9945 				}, 'items', t);
9946 			}
9947 
9948 			t.collapsed = false;
9949 		},
9950 
9951 		collapse : function(d) {
9952 			var t = this;
9953 
9954 			if (d) {
9955 				walk(t, function(o) {
9956 					if (o.collapse)
9957 						o.collapse();
9958 				}, 'items', t);
9959 			}
9960 
9961 			t.collapsed = true;
9962 		},
9963 
9964 		isCollapsed : function() {
9965 			return this.collapsed;
9966 		},
9967 
9968 		add : function(o) {
9969 			if (!o.settings)
9970 				o = new tinymce.ui.MenuItem(o.id || DOM.uniqueId(), o);
9971 
9972 			this.onAddItem.dispatch(this, o);
9973 
9974 			return this.items[o.id] = o;
9975 		},
9976 
9977 		addSeparator : function() {
9978 			return this.add({separator : true});
9979 		},
9980 
9981 		addMenu : function(o) {
9982 			if (!o.collapse)
9983 				o = this.createMenu(o);
9984 
9985 			this.menuCount++;
9986 
9987 			return this.add(o);
9988 		},
9989 
9990 		hasMenus : function() {
9991 			return this.menuCount !== 0;
9992 		},
9993 
9994 		remove : function(o) {
9995 			delete this.items[o.id];
9996 		},
9997 
9998 		removeAll : function() {
9999 			var t = this;
10000 
10001 			walk(t, function(o) {
10002 				if (o.removeAll)
10003 					o.removeAll();
10004 				else
10005 					o.remove();
10006 
10007 				o.destroy();
10008 			}, 'items', t);
10009 
10010 			t.items = {};
10011 		},
10012 
10013 		createMenu : function(o) {
10014 			var m = new tinymce.ui.Menu(o.id || DOM.uniqueId(), o);
10015 
10016 			m.onAddItem.add(this.onAddItem.dispatch, this.onAddItem);
10017 
10018 			return m;
10019 		}
10020 	});
10021 })(tinymce);
10022 (function(tinymce) {
10023 	var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event, Element = tinymce.dom.Element;
10024 
10025 	tinymce.create('tinymce.ui.DropMenu:tinymce.ui.Menu', {
10026 		DropMenu : function(id, s) {
10027 			s = s || {};
10028 			s.container = s.container || DOM.doc.body;
10029 			s.offset_x = s.offset_x || 0;
10030 			s.offset_y = s.offset_y || 0;
10031 			s.vp_offset_x = s.vp_offset_x || 0;
10032 			s.vp_offset_y = s.vp_offset_y || 0;
10033 
10034 			if (is(s.icons) && !s.icons)
10035 				s['class'] += ' mceNoIcons';
10036 
10037 			this.parent(id, s);
10038 			this.onShowMenu = new tinymce.util.Dispatcher(this);
10039 			this.onHideMenu = new tinymce.util.Dispatcher(this);
10040 			this.classPrefix = 'mceMenu';
10041 		},
10042 
10043 		createMenu : function(s) {
10044 			var t = this, cs = t.settings, m;
10045 
10046 			s.container = s.container || cs.container;
10047 			s.parent = t;
10048 			s.constrain = s.constrain || cs.constrain;
10049 			s['class'] = s['class'] || cs['class'];
10050 			s.vp_offset_x = s.vp_offset_x || cs.vp_offset_x;
10051 			s.vp_offset_y = s.vp_offset_y || cs.vp_offset_y;
10052 			s.keyboard_focus = cs.keyboard_focus;
10053 			m = new tinymce.ui.DropMenu(s.id || DOM.uniqueId(), s);
10054 
10055 			m.onAddItem.add(t.onAddItem.dispatch, t.onAddItem);
10056 
10057 			return m;
10058 		},
10059 		
10060 		focus : function() {
10061 			var t = this;
10062 			if (t.keyboardNav) {
10063 				t.keyboardNav.focus();
10064 			}
10065 		},
10066 
10067 		update : function() {
10068 			var t = this, s = t.settings, tb = DOM.get('menu_' + t.id + '_tbl'), co = DOM.get('menu_' + t.id + '_co'), tw, th;
10069 
10070 			tw = s.max_width ? Math.min(tb.offsetWidth, s.max_width) : tb.offsetWidth;
10071 			th = s.max_height ? Math.min(tb.offsetHeight, s.max_height) : tb.offsetHeight;
10072 
10073 			if (!DOM.boxModel)
10074 				t.element.setStyles({width : tw + 2, height : th + 2});
10075 			else
10076 				t.element.setStyles({width : tw, height : th});
10077 
10078 			if (s.max_width)
10079 				DOM.setStyle(co, 'width', tw);
10080 
10081 			if (s.max_height) {
10082 				DOM.setStyle(co, 'height', th);
10083 
10084 				if (tb.clientHeight < s.max_height)
10085 					DOM.setStyle(co, 'overflow', 'hidden');
10086 			}
10087 		},
10088 
10089 		showMenu : function(x, y, px) {
10090 			var t = this, s = t.settings, co, vp = DOM.getViewPort(), w, h, mx, my, ot = 2, dm, tb, cp = t.classPrefix;
10091 
10092 			t.collapse(1);
10093 
10094 			if (t.isMenuVisible)
10095 				return;
10096 
10097 			if (!t.rendered) {
10098 				co = DOM.add(t.settings.container, t.renderNode());
10099 
10100 				each(t.items, function(o) {
10101 					o.postRender();
10102 				});
10103 
10104 				t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
10105 			} else
10106 				co = DOM.get('menu_' + t.id);
10107 
10108 			// Move layer out of sight unless it's Opera since it scrolls to top of page due to an bug
10109 			if (!tinymce.isOpera)
10110 				DOM.setStyles(co, {left : -0xFFFF , top : -0xFFFF});
10111 
10112 			DOM.show(co);
10113 			t.update();
10114 
10115 			x += s.offset_x || 0;
10116 			y += s.offset_y || 0;
10117 			vp.w -= 4;
10118 			vp.h -= 4;
10119 
10120 			// Move inside viewport if not submenu
10121 			if (s.constrain) {
10122 				w = co.clientWidth - ot;
10123 				h = co.clientHeight - ot;
10124 				mx = vp.x + vp.w;
10125 				my = vp.y + vp.h;
10126 
10127 				if ((x + s.vp_offset_x + w) > mx)
10128 					x = px ? px - w : Math.max(0, (mx - s.vp_offset_x) - w);
10129 
10130 				if ((y + s.vp_offset_y + h) > my)
10131 					y = Math.max(0, (my - s.vp_offset_y) - h);
10132 			}
10133 
10134 			DOM.setStyles(co, {left : x , top : y});
10135 			t.element.update();
10136 
10137 			t.isMenuVisible = 1;
10138 			t.mouseClickFunc = Event.add(co, 'click', function(e) {
10139 				var m;
10140 
10141 				e = e.target;
10142 
10143 				if (e && (e = DOM.getParent(e, 'tr')) && !DOM.hasClass(e, cp + 'ItemSub')) {
10144 					m = t.items[e.id];
10145 
10146 					if (m.isDisabled())
10147 						return;
10148 
10149 					dm = t;
10150 
10151 					while (dm) {
10152 						if (dm.hideMenu)
10153 							dm.hideMenu();
10154 
10155 						dm = dm.settings.parent;
10156 					}
10157 
10158 					if (m.settings.onclick)
10159 						m.settings.onclick(e);
10160 
10161 					return false; // Cancel to fix onbeforeunload problem
10162 				}
10163 			});
10164 
10165 			if (t.hasMenus()) {
10166 				t.mouseOverFunc = Event.add(co, 'mouseover', function(e) {
10167 					var m, r, mi;
10168 
10169 					e = e.target;
10170 					if (e && (e = DOM.getParent(e, 'tr'))) {
10171 						m = t.items[e.id];
10172 
10173 						if (t.lastMenu)
10174 							t.lastMenu.collapse(1);
10175 
10176 						if (m.isDisabled())
10177 							return;
10178 
10179 						if (e && DOM.hasClass(e, cp + 'ItemSub')) {
10180 							//p = DOM.getPos(s.container);
10181 							r = DOM.getRect(e);
10182 							m.showMenu((r.x + r.w - ot), r.y - ot, r.x);
10183 							t.lastMenu = m;
10184 							DOM.addClass(DOM.get(m.id).firstChild, cp + 'ItemActive');
10185 						}
10186 					}
10187 				});
10188 			}
10189 			
10190 			Event.add(co, 'keydown', t._keyHandler, t);
10191 
10192 			t.onShowMenu.dispatch(t);
10193 
10194 			if (s.keyboard_focus) { 
10195 				t._setupKeyboardNav(); 
10196 			}
10197 		},
10198 
10199 		hideMenu : function(c) {
10200 			var t = this, co = DOM.get('menu_' + t.id), e;
10201 
10202 			if (!t.isMenuVisible)
10203 				return;
10204 
10205 			if (t.keyboardNav) t.keyboardNav.destroy();
10206 			Event.remove(co, 'mouseover', t.mouseOverFunc);
10207 			Event.remove(co, 'click', t.mouseClickFunc);
10208 			Event.remove(co, 'keydown', t._keyHandler);
10209 			DOM.hide(co);
10210 			t.isMenuVisible = 0;
10211 
10212 			if (!c)
10213 				t.collapse(1);
10214 
10215 			if (t.element)
10216 				t.element.hide();
10217 
10218 			if (e = DOM.get(t.id))
10219 				DOM.removeClass(e.firstChild, t.classPrefix + 'ItemActive');
10220 
10221 			t.onHideMenu.dispatch(t);
10222 		},
10223 
10224 		add : function(o) {
10225 			var t = this, co;
10226 
10227 			o = t.parent(o);
10228 
10229 			if (t.isRendered && (co = DOM.get('menu_' + t.id)))
10230 				t._add(DOM.select('tbody', co)[0], o);
10231 
10232 			return o;
10233 		},
10234 
10235 		collapse : function(d) {
10236 			this.parent(d);
10237 			this.hideMenu(1);
10238 		},
10239 
10240 		remove : function(o) {
10241 			DOM.remove(o.id);
10242 			this.destroy();
10243 
10244 			return this.parent(o);
10245 		},
10246 
10247 		destroy : function() {
10248 			var t = this, co = DOM.get('menu_' + t.id);
10249 
10250 			if (t.keyboardNav) t.keyboardNav.destroy();
10251 			Event.remove(co, 'mouseover', t.mouseOverFunc);
10252 			Event.remove(DOM.select('a', co), 'focus', t.mouseOverFunc);
10253 			Event.remove(co, 'click', t.mouseClickFunc);
10254 			Event.remove(co, 'keydown', t._keyHandler);
10255 
10256 			if (t.element)
10257 				t.element.remove();
10258 
10259 			DOM.remove(co);
10260 		},
10261 
10262 		renderNode : function() {
10263 			var t = this, s = t.settings, n, tb, co, w;
10264 
10265 			w = DOM.create('div', {role: 'listbox', id : 'menu_' + t.id, 'class' : s['class'], 'style' : 'position:absolute;left:0;top:0;z-index:200000;outline:0'});
10266 			if (t.settings.parent) {
10267 				DOM.setAttrib(w, 'aria-parent', 'menu_' + t.settings.parent.id);
10268 			}
10269 			co = DOM.add(w, 'div', {role: 'presentation', id : 'menu_' + t.id + '_co', 'class' : t.classPrefix + (s['class'] ? ' ' + s['class'] : '')});
10270 			t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
10271 
10272 			if (s.menu_line)
10273 				DOM.add(co, 'span', {'class' : t.classPrefix + 'Line'});
10274 
10275 //			n = DOM.add(co, 'div', {id : 'menu_' + t.id + '_co', 'class' : 'mceMenuContainer'});
10276 			n = DOM.add(co, 'table', {role: 'presentation', id : 'menu_' + t.id + '_tbl', border : 0, cellPadding : 0, cellSpacing : 0});
10277 			tb = DOM.add(n, 'tbody');
10278 
10279 			each(t.items, function(o) {
10280 				t._add(tb, o);
10281 			});
10282 
10283 			t.rendered = true;
10284 
10285 			return w;
10286 		},
10287 
10288 		// Internal functions
10289 		_setupKeyboardNav : function(){
10290 			var contextMenu, menuItems, t=this; 
10291 			contextMenu = DOM.get('menu_' + t.id);
10292 			menuItems = DOM.select('a[role=option]', 'menu_' + t.id);
10293 			menuItems.splice(0,0,contextMenu);
10294 			t.keyboardNav = new tinymce.ui.KeyboardNavigation({
10295 				root: 'menu_' + t.id,
10296 				items: menuItems,
10297 				onCancel: function() {
10298 					t.hideMenu();
10299 				},
10300 				enableUpDown: true
10301 			});
10302 			contextMenu.focus();
10303 		},
10304 
10305 		_keyHandler : function(evt) {
10306 			var t = this, e;
10307 			switch (evt.keyCode) {
10308 				case 37: // Left
10309 					if (t.settings.parent) {
10310 						t.hideMenu();
10311 						t.settings.parent.focus();
10312 						Event.cancel(evt);
10313 					}
10314 					break;
10315 				case 39: // Right
10316 					if (t.mouseOverFunc)
10317 						t.mouseOverFunc(evt);
10318 					break;
10319 			}
10320 		},
10321 
10322 		_add : function(tb, o) {
10323 			var n, s = o.settings, a, ro, it, cp = this.classPrefix, ic;
10324 
10325 			if (s.separator) {
10326 				ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'ItemSeparator'});
10327 				DOM.add(ro, 'td', {'class' : cp + 'ItemSeparator'});
10328 
10329 				if (n = ro.previousSibling)
10330 					DOM.addClass(n, 'mceLast');
10331 
10332 				return;
10333 			}
10334 
10335 			n = ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'Item ' + cp + 'ItemEnabled'});
10336 			n = it = DOM.add(n, s.titleItem ? 'th' : 'td');
10337 			n = a = DOM.add(n, 'a', {id: o.id + '_aria',  role: s.titleItem ? 'presentation' : 'option', href : 'javascript:;', onclick : "return false;", onmousedown : 'return false;'});
10338 
10339 			if (s.parent) {
10340 				DOM.setAttrib(a, 'aria-haspopup', 'true');
10341 				DOM.setAttrib(a, 'aria-owns', 'menu_' + o.id);
10342 			}
10343 
10344 			DOM.addClass(it, s['class']);
10345 //			n = DOM.add(n, 'span', {'class' : 'item'});
10346 
10347 			ic = DOM.add(n, 'span', {'class' : 'mceIcon' + (s.icon ? ' mce_' + s.icon : '')});
10348 
10349 			if (s.icon_src)
10350 				DOM.add(ic, 'img', {src : s.icon_src});
10351 
10352 			n = DOM.add(n, s.element || 'span', {'class' : 'mceText', title : o.settings.title}, o.settings.title);
10353 
10354 			if (o.settings.style) {
10355 				if (typeof o.settings.style == "function")
10356 					o.settings.style = o.settings.style();
10357 
10358 				DOM.setAttrib(n, 'style', o.settings.style);
10359 			}
10360 
10361 			if (tb.childNodes.length == 1)
10362 				DOM.addClass(ro, 'mceFirst');
10363 
10364 			if ((n = ro.previousSibling) && DOM.hasClass(n, cp + 'ItemSeparator'))
10365 				DOM.addClass(ro, 'mceFirst');
10366 
10367 			if (o.collapse)
10368 				DOM.addClass(ro, cp + 'ItemSub');
10369 
10370 			if (n = ro.previousSibling)
10371 				DOM.removeClass(n, 'mceLast');
10372 
10373 			DOM.addClass(ro, 'mceLast');
10374 		}
10375 	});
10376 })(tinymce);
10377 (function(tinymce) {
10378 	var DOM = tinymce.DOM;
10379 
10380 	tinymce.create('tinymce.ui.Button:tinymce.ui.Control', {
10381 		Button : function(id, s, ed) {
10382 			this.parent(id, s, ed);
10383 			this.classPrefix = 'mceButton';
10384 		},
10385 
10386 		renderHTML : function() {
10387 			var cp = this.classPrefix, s = this.settings, h, l;
10388 
10389 			l = DOM.encode(s.label || '');
10390 			h = '<a role="button" id="' + this.id + '" href="javascript:;" class="' + cp + ' ' + cp + 'Enabled ' + s['class'] + (l ? ' ' + cp + 'Labeled' : '') +'" onmousedown="return false;" onclick="return false;" aria-labelledby="' + this.id + '_voice" title="' + DOM.encode(s.title) + '">';
10391 			if (s.image && !(this.editor  &&this.editor.forcedHighContrastMode) )
10392 				h += '<span class="mceIcon ' + s['class'] + '"><img class="mceIcon" src="' + s.image + '" alt="' + DOM.encode(s.title) + '" /></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : '');
10393 			else
10394 				h += '<span class="mceIcon ' + s['class'] + '"></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : '');
10395 
10396 			h += '<span class="mceVoiceLabel mceIconOnly" style="display: none;" id="' + this.id + '_voice">' + s.title + '</span>'; 
10397 			h += '</a>';
10398 			return h;
10399 		},
10400 
10401 		postRender : function() {
10402 			var t = this, s = t.settings, imgBookmark;
10403 
10404 			// In IE a large image that occupies the entire editor area will be deselected when a button is clicked, so
10405 			// need to keep the selection in case the selection is lost
10406 			if (tinymce.isIE && t.editor) {
10407 				tinymce.dom.Event.add(t.id, 'mousedown', function(e) {
10408 					var nodeName = t.editor.selection.getNode().nodeName;
10409 					imgBookmark = nodeName === 'IMG' ? t.editor.selection.getBookmark() : null;
10410 				});
10411 			}
10412 			tinymce.dom.Event.add(t.id, 'click', function(e) {
10413 				if (!t.isDisabled()) {
10414 					// restore the selection in case the selection is lost in IE
10415 					if (tinymce.isIE && t.editor && imgBookmark !== null) {
10416 						t.editor.selection.moveToBookmark(imgBookmark);
10417 					}
10418 					return s.onclick.call(s.scope, e);
10419 				}
10420 			});
10421 			tinymce.dom.Event.add(t.id, 'keyup', function(e) {
10422 				if (!t.isDisabled() && e.keyCode==tinymce.VK.SPACEBAR)
10423 					return s.onclick.call(s.scope, e);
10424 			});
10425 		}
10426 	});
10427 })(tinymce);
10428 
10429 (function(tinymce) {
10430 	var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher, undef;
10431 
10432 	tinymce.create('tinymce.ui.ListBox:tinymce.ui.Control', {
10433 		ListBox : function(id, s, ed) {
10434 			var t = this;
10435 
10436 			t.parent(id, s, ed);
10437 
10438 			t.items = [];
10439 
10440 			t.onChange = new Dispatcher(t);
10441 
10442 			t.onPostRender = new Dispatcher(t);
10443 
10444 			t.onAdd = new Dispatcher(t);
10445 
10446 			t.onRenderMenu = new tinymce.util.Dispatcher(this);
10447 
10448 			t.classPrefix = 'mceListBox';
10449 			t.marked = {};
10450 		},
10451 
10452 		select : function(va) {
10453 			var t = this, fv, f;
10454 
10455 			t.marked = {};
10456 
10457 			if (va == undef)
10458 				return t.selectByIndex(-1);
10459 
10460 			// Is string or number make function selector
10461 			if (va && typeof(va)=="function")
10462 				f = va;
10463 			else {
10464 				f = function(v) {
10465 					return v == va;
10466 				};
10467 			}
10468 
10469 			// Do we need to do something?
10470 			if (va != t.selectedValue) {
10471 				// Find item
10472 				each(t.items, function(o, i) {
10473 					if (f(o.value)) {
10474 						fv = 1;
10475 						t.selectByIndex(i);
10476 						return false;
10477 					}
10478 				});
10479 
10480 				if (!fv)
10481 					t.selectByIndex(-1);
10482 			}
10483 		},
10484 
10485 		selectByIndex : function(idx) {
10486 			var t = this, e, o, label;
10487 
10488 			t.marked = {};
10489 
10490 			if (idx != t.selectedIndex) {
10491 				e = DOM.get(t.id + '_text');
10492 				label = DOM.get(t.id + '_voiceDesc');
10493 				o = t.items[idx];
10494 
10495 				if (o) {
10496 					t.selectedValue = o.value;
10497 					t.selectedIndex = idx;
10498 					DOM.setHTML(e, DOM.encode(o.title));
10499 					DOM.setHTML(label, t.settings.title + " - " + o.title);
10500 					DOM.removeClass(e, 'mceTitle');
10501 					DOM.setAttrib(t.id, 'aria-valuenow', o.title);
10502 				} else {
10503 					DOM.setHTML(e, DOM.encode(t.settings.title));
10504 					DOM.setHTML(label, DOM.encode(t.settings.title));
10505 					DOM.addClass(e, 'mceTitle');
10506 					t.selectedValue = t.selectedIndex = null;
10507 					DOM.setAttrib(t.id, 'aria-valuenow', t.settings.title);
10508 				}
10509 				e = 0;
10510 			}
10511 		},
10512 
10513 		mark : function(value) {
10514 			this.marked[value] = true;
10515 		},
10516 
10517 		add : function(n, v, o) {
10518 			var t = this;
10519 
10520 			o = o || {};
10521 			o = tinymce.extend(o, {
10522 				title : n,
10523 				value : v
10524 			});
10525 
10526 			t.items.push(o);
10527 			t.onAdd.dispatch(t, o);
10528 		},
10529 
10530 		getLength : function() {
10531 			return this.items.length;
10532 		},
10533 
10534 		renderHTML : function() {
10535 			var h = '', t = this, s = t.settings, cp = t.classPrefix;
10536 
10537 			h = '<span role="listbox" aria-haspopup="true" aria-labelledby="' + t.id +'_voiceDesc" aria-describedby="' + t.id + '_voiceDesc"><table role="presentation" tabindex="0" id="' + t.id + '" cellpadding="0" cellspacing="0" class="' + cp + ' ' + cp + 'Enabled' + (s['class'] ? (' ' + s['class']) : '') + '"><tbody><tr>';
10538 			h += '<td>' + DOM.createHTML('span', {id: t.id + '_voiceDesc', 'class': 'voiceLabel', style:'display:none;'}, t.settings.title); 
10539 			h += DOM.createHTML('a', {id : t.id + '_text', tabindex : -1, href : 'javascript:;', 'class' : 'mceText', onclick : "return false;", onmousedown : 'return false;'}, DOM.encode(t.settings.title)) + '</td>';
10540 			h += '<td>' + DOM.createHTML('a', {id : t.id + '_open', tabindex : -1, href : 'javascript:;', 'class' : 'mceOpen', onclick : "return false;", onmousedown : 'return false;'}, '<span><span style="display:none;" class="mceIconOnly" aria-hidden="true">\u25BC</span></span>') + '</td>';
10541 			h += '</tr></tbody></table></span>';
10542 
10543 			return h;
10544 		},
10545 
10546 		showMenu : function() {
10547 			var t = this, p2, e = DOM.get(this.id), m;
10548 
10549 			if (t.isDisabled() || t.items.length === 0)
10550 				return;
10551 
10552 			if (t.menu && t.menu.isMenuVisible)
10553 				return t.hideMenu();
10554 
10555 			if (!t.isMenuRendered) {
10556 				t.renderMenu();
10557 				t.isMenuRendered = true;
10558 			}
10559 
10560 			p2 = DOM.getPos(e);
10561 
10562 			m = t.menu;
10563 			m.settings.offset_x = p2.x;
10564 			m.settings.offset_y = p2.y;
10565 			m.settings.keyboard_focus = !tinymce.isOpera; // Opera is buggy when it comes to auto focus
10566 
10567 			// Select in menu
10568 			each(t.items, function(o) {
10569 				if (m.items[o.id]) {
10570 					m.items[o.id].setSelected(0);
10571 				}
10572 			});
10573 
10574 			each(t.items, function(o) {
10575 				if (m.items[o.id] && t.marked[o.value]) {
10576 					m.items[o.id].setSelected(1);
10577 				}
10578 
10579 				if (o.value === t.selectedValue) {
10580 					m.items[o.id].setSelected(1);
10581 				}
10582 			});
10583 
10584 			m.showMenu(0, e.clientHeight);
10585 
10586 			Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
10587 			DOM.addClass(t.id, t.classPrefix + 'Selected');
10588 
10589 			//DOM.get(t.id + '_text').focus();
10590 		},
10591 
10592 		hideMenu : function(e) {
10593 			var t = this;
10594 
10595 			if (t.menu && t.menu.isMenuVisible) {
10596 				DOM.removeClass(t.id, t.classPrefix + 'Selected');
10597 
10598 				// Prevent double toogles by canceling the mouse click event to the button
10599 				if (e && e.type == "mousedown" && (e.target.id == t.id + '_text' || e.target.id == t.id + '_open'))
10600 					return;
10601 
10602 				if (!e || !DOM.getParent(e.target, '.mceMenu')) {
10603 					DOM.removeClass(t.id, t.classPrefix + 'Selected');
10604 					Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
10605 					t.menu.hideMenu();
10606 				}
10607 			}
10608 		},
10609 
10610 		renderMenu : function() {
10611 			var t = this, m;
10612 
10613 			m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
10614 				menu_line : 1,
10615 				'class' : t.classPrefix + 'Menu mceNoIcons',
10616 				max_width : 250,
10617 				max_height : 150
10618 			});
10619 
10620 			m.onHideMenu.add(function() {
10621 				t.hideMenu();
10622 				t.focus();
10623 			});
10624 
10625 			m.add({
10626 				title : t.settings.title,
10627 				'class' : 'mceMenuItemTitle',
10628 				onclick : function() {
10629 					if (t.settings.onselect('') !== false)
10630 						t.select(''); // Must be runned after
10631 				}
10632 			});
10633 
10634 			each(t.items, function(o) {
10635 				// No value then treat it as a title
10636 				if (o.value === undef) {
10637 					m.add({
10638 						title : o.title,
10639 						role : "option",
10640 						'class' : 'mceMenuItemTitle',
10641 						onclick : function() {
10642 							if (t.settings.onselect('') !== false)
10643 								t.select(''); // Must be runned after
10644 						}
10645 					});
10646 				} else {
10647 					o.id = DOM.uniqueId();
10648 					o.role= "option";
10649 					o.onclick = function() {
10650 						if (t.settings.onselect(o.value) !== false)
10651 							t.select(o.value); // Must be runned after
10652 					};
10653 
10654 					m.add(o);
10655 				}
10656 			});
10657 
10658 			t.onRenderMenu.dispatch(t, m);
10659 			t.menu = m;
10660 		},
10661 
10662 		postRender : function() {
10663 			var t = this, cp = t.classPrefix;
10664 
10665 			Event.add(t.id, 'click', t.showMenu, t);
10666 			Event.add(t.id, 'keydown', function(evt) {
10667 				if (evt.keyCode == 32) { // Space
10668 					t.showMenu(evt);
10669 					Event.cancel(evt);
10670 				}
10671 			});
10672 			Event.add(t.id, 'focus', function() {
10673 				if (!t._focused) {
10674 					t.keyDownHandler = Event.add(t.id, 'keydown', function(e) {
10675 						if (e.keyCode == 40) {
10676 							t.showMenu();
10677 							Event.cancel(e);
10678 						}
10679 					});
10680 					t.keyPressHandler = Event.add(t.id, 'keypress', function(e) {
10681 						var v;
10682 						if (e.keyCode == 13) {
10683 							// Fake select on enter
10684 							v = t.selectedValue;
10685 							t.selectedValue = null; // Needs to be null to fake change
10686 							Event.cancel(e);
10687 							t.settings.onselect(v);
10688 						}
10689 					});
10690 				}
10691 
10692 				t._focused = 1;
10693 			});
10694 			Event.add(t.id, 'blur', function() {
10695 				Event.remove(t.id, 'keydown', t.keyDownHandler);
10696 				Event.remove(t.id, 'keypress', t.keyPressHandler);
10697 				t._focused = 0;
10698 			});
10699 
10700 			// Old IE doesn't have hover on all elements
10701 			if (tinymce.isIE6 || !DOM.boxModel) {
10702 				Event.add(t.id, 'mouseover', function() {
10703 					if (!DOM.hasClass(t.id, cp + 'Disabled'))
10704 						DOM.addClass(t.id, cp + 'Hover');
10705 				});
10706 
10707 				Event.add(t.id, 'mouseout', function() {
10708 					if (!DOM.hasClass(t.id, cp + 'Disabled'))
10709 						DOM.removeClass(t.id, cp + 'Hover');
10710 				});
10711 			}
10712 
10713 			t.onPostRender.dispatch(t, DOM.get(t.id));
10714 		},
10715 
10716 		destroy : function() {
10717 			this.parent();
10718 
10719 			Event.clear(this.id + '_text');
10720 			Event.clear(this.id + '_open');
10721 		}
10722 	});
10723 })(tinymce);
10724 
10725 (function(tinymce) {
10726 	var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher, undef;
10727 
10728 	tinymce.create('tinymce.ui.NativeListBox:tinymce.ui.ListBox', {
10729 		NativeListBox : function(id, s) {
10730 			this.parent(id, s);
10731 			this.classPrefix = 'mceNativeListBox';
10732 		},
10733 
10734 		setDisabled : function(s) {
10735 			DOM.get(this.id).disabled = s;
10736 			this.setAriaProperty('disabled', s);
10737 		},
10738 
10739 		isDisabled : function() {
10740 			return DOM.get(this.id).disabled;
10741 		},
10742 
10743 		select : function(va) {
10744 			var t = this, fv, f;
10745 
10746 			if (va == undef)
10747 				return t.selectByIndex(-1);
10748 
10749 			// Is string or number make function selector
10750 			if (va && typeof(va)=="function")
10751 				f = va;
10752 			else {
10753 				f = function(v) {
10754 					return v == va;
10755 				};
10756 			}
10757 
10758 			// Do we need to do something?
10759 			if (va != t.selectedValue) {
10760 				// Find item
10761 				each(t.items, function(o, i) {
10762 					if (f(o.value)) {
10763 						fv = 1;
10764 						t.selectByIndex(i);
10765 						return false;
10766 					}
10767 				});
10768 
10769 				if (!fv)
10770 					t.selectByIndex(-1);
10771 			}
10772 		},
10773 
10774 		selectByIndex : function(idx) {
10775 			DOM.get(this.id).selectedIndex = idx + 1;
10776 			this.selectedValue = this.items[idx] ? this.items[idx].value : null;
10777 		},
10778 
10779 		add : function(n, v, a) {
10780 			var o, t = this;
10781 
10782 			a = a || {};
10783 			a.value = v;
10784 
10785 			if (t.isRendered())
10786 				DOM.add(DOM.get(this.id), 'option', a, n);
10787 
10788 			o = {
10789 				title : n,
10790 				value : v,
10791 				attribs : a
10792 			};
10793 
10794 			t.items.push(o);
10795 			t.onAdd.dispatch(t, o);
10796 		},
10797 
10798 		getLength : function() {
10799 			return this.items.length;
10800 		},
10801 
10802 		renderHTML : function() {
10803 			var h, t = this;
10804 
10805 			h = DOM.createHTML('option', {value : ''}, '-- ' + t.settings.title + ' --');
10806 
10807 			each(t.items, function(it) {
10808 				h += DOM.createHTML('option', {value : it.value}, it.title);
10809 			});
10810 
10811 			h = DOM.createHTML('select', {id : t.id, 'class' : 'mceNativeListBox', 'aria-labelledby': t.id + '_aria'}, h);
10812 			h += DOM.createHTML('span', {id : t.id + '_aria', 'style': 'display: none'}, t.settings.title);
10813 			return h;
10814 		},
10815 
10816 		postRender : function() {
10817 			var t = this, ch, changeListenerAdded = true;
10818 
10819 			t.rendered = true;
10820 
10821 			function onChange(e) {
10822 				var v = t.items[e.target.selectedIndex - 1];
10823 
10824 				if (v && (v = v.value)) {
10825 					t.onChange.dispatch(t, v);
10826 
10827 					if (t.settings.onselect)
10828 						t.settings.onselect(v);
10829 				}
10830 			};
10831 
10832 			Event.add(t.id, 'change', onChange);
10833 
10834 			// Accessibility keyhandler
10835 			Event.add(t.id, 'keydown', function(e) {
10836 				var bf;
10837 
10838 				Event.remove(t.id, 'change', ch);
10839 				changeListenerAdded = false;
10840 
10841 				bf = Event.add(t.id, 'blur', function() {
10842 					if (changeListenerAdded) return;
10843 					changeListenerAdded = true;
10844 					Event.add(t.id, 'change', onChange);
10845 					Event.remove(t.id, 'blur', bf);
10846 				});
10847 
10848 				//prevent default left and right keys on chrome - so that the keyboard navigation is used.
10849 				if (tinymce.isWebKit && (e.keyCode==37 ||e.keyCode==39)) {
10850 					return Event.prevent(e);
10851 				}
10852 				
10853 				if (e.keyCode == 13 || e.keyCode == 32) {
10854 					onChange(e);
10855 					return Event.cancel(e);
10856 				}
10857 			});
10858 
10859 			t.onPostRender.dispatch(t, DOM.get(t.id));
10860 		}
10861 	});
10862 })(tinymce);
10863 
10864 (function(tinymce) {
10865 	var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
10866 
10867 	tinymce.create('tinymce.ui.MenuButton:tinymce.ui.Button', {
10868 		MenuButton : function(id, s, ed) {
10869 			this.parent(id, s, ed);
10870 
10871 			this.onRenderMenu = new tinymce.util.Dispatcher(this);
10872 
10873 			s.menu_container = s.menu_container || DOM.doc.body;
10874 		},
10875 
10876 		showMenu : function() {
10877 			var t = this, p1, p2, e = DOM.get(t.id), m;
10878 
10879 			if (t.isDisabled())
10880 				return;
10881 
10882 			if (!t.isMenuRendered) {
10883 				t.renderMenu();
10884 				t.isMenuRendered = true;
10885 			}
10886 
10887 			if (t.isMenuVisible)
10888 				return t.hideMenu();
10889 
10890 			p1 = DOM.getPos(t.settings.menu_container);
10891 			p2 = DOM.getPos(e);
10892 
10893 			m = t.menu;
10894 			m.settings.offset_x = p2.x;
10895 			m.settings.offset_y = p2.y;
10896 			m.settings.vp_offset_x = p2.x;
10897 			m.settings.vp_offset_y = p2.y;
10898 			m.settings.keyboard_focus = t._focused;
10899 			m.showMenu(0, e.firstChild.clientHeight);
10900 
10901 			Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
10902 			t.setState('Selected', 1);
10903 
10904 			t.isMenuVisible = 1;
10905 		},
10906 
10907 		renderMenu : function() {
10908 			var t = this, m;
10909 
10910 			m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
10911 				menu_line : 1,
10912 				'class' : this.classPrefix + 'Menu',
10913 				icons : t.settings.icons
10914 			});
10915 
10916 			m.onHideMenu.add(function() {
10917 				t.hideMenu();
10918 				t.focus();
10919 			});
10920 
10921 			t.onRenderMenu.dispatch(t, m);
10922 			t.menu = m;
10923 		},
10924 
10925 		hideMenu : function(e) {
10926 			var t = this;
10927 
10928 			// Prevent double toogles by canceling the mouse click event to the button
10929 			if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id || e.id === t.id + '_open';}))
10930 				return;
10931 
10932 			if (!e || !DOM.getParent(e.target, '.mceMenu')) {
10933 				t.setState('Selected', 0);
10934 				Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
10935 				if (t.menu)
10936 					t.menu.hideMenu();
10937 			}
10938 
10939 			t.isMenuVisible = 0;
10940 		},
10941 
10942 		postRender : function() {
10943 			var t = this, s = t.settings;
10944 
10945 			Event.add(t.id, 'click', function() {
10946 				if (!t.isDisabled()) {
10947 					if (s.onclick)
10948 						s.onclick(t.value);
10949 
10950 					t.showMenu();
10951 				}
10952 			});
10953 		}
10954 	});
10955 })(tinymce);
10956 
10957 (function(tinymce) {
10958 	var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
10959 
10960 	tinymce.create('tinymce.ui.SplitButton:tinymce.ui.MenuButton', {
10961 		SplitButton : function(id, s, ed) {
10962 			this.parent(id, s, ed);
10963 			this.classPrefix = 'mceSplitButton';
10964 		},
10965 
10966 		renderHTML : function() {
10967 			var h, t = this, s = t.settings, h1;
10968 
10969 			h = '<tbody><tr>';
10970 
10971 			if (s.image)
10972 				h1 = DOM.createHTML('img ', {src : s.image, role: 'presentation', 'class' : 'mceAction ' + s['class']});
10973 			else
10974 				h1 = DOM.createHTML('span', {'class' : 'mceAction ' + s['class']}, '');
10975 
10976 			h1 += DOM.createHTML('span', {'class': 'mceVoiceLabel mceIconOnly', id: t.id + '_voice', style: 'display:none;'}, s.title);
10977 			h += '<td >' + DOM.createHTML('a', {role: 'button', id : t.id + '_action', tabindex: '-1', href : 'javascript:;', 'class' : 'mceAction ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + '</td>';
10978 	
10979 			h1 = DOM.createHTML('span', {'class' : 'mceOpen ' + s['class']}, '<span style="display:none;" class="mceIconOnly" aria-hidden="true">\u25BC</span>');
10980 			h += '<td >' + DOM.createHTML('a', {role: 'button', id : t.id + '_open', tabindex: '-1', href : 'javascript:;', 'class' : 'mceOpen ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + '</td>';
10981 
10982 			h += '</tr></tbody>';
10983 			h = DOM.createHTML('table', { role: 'presentation',   'class' : 'mceSplitButton mceSplitButtonEnabled ' + s['class'], cellpadding : '0', cellspacing : '0', title : s.title}, h);
10984 			return DOM.createHTML('div', {id : t.id, role: 'button', tabindex: '0', 'aria-labelledby': t.id + '_voice', 'aria-haspopup': 'true'}, h);
10985 		},
10986 
10987 		postRender : function() {
10988 			var t = this, s = t.settings, activate;
10989 
10990 			if (s.onclick) {
10991 				activate = function(evt) {
10992 					if (!t.isDisabled()) {
10993 						s.onclick(t.value);
10994 						Event.cancel(evt);
10995 					}
10996 				};
10997 				Event.add(t.id + '_action', 'click', activate);
10998 				Event.add(t.id, ['click', 'keydown'], function(evt) {
10999 					var DOM_VK_SPACE = 32, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_UP = 38, DOM_VK_DOWN = 40;
11000 					if ((evt.keyCode === 32 || evt.keyCode === 13 || evt.keyCode === 14) && !evt.altKey && !evt.ctrlKey && !evt.metaKey) {
11001 						activate();
11002 						Event.cancel(evt);
11003 					} else if (evt.type === 'click' || evt.keyCode === DOM_VK_DOWN) {
11004 						t.showMenu();
11005 						Event.cancel(evt);
11006 					}
11007 				});
11008 			}
11009 
11010 			Event.add(t.id + '_open', 'click', function (evt) {
11011 				t.showMenu();
11012 				Event.cancel(evt);
11013 			});
11014 			Event.add([t.id, t.id + '_open'], 'focus', function() {t._focused = 1;});
11015 			Event.add([t.id, t.id + '_open'], 'blur', function() {t._focused = 0;});
11016 
11017 			// Old IE doesn't have hover on all elements
11018 			if (tinymce.isIE6 || !DOM.boxModel) {
11019 				Event.add(t.id, 'mouseover', function() {
11020 					if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
11021 						DOM.addClass(t.id, 'mceSplitButtonHover');
11022 				});
11023 
11024 				Event.add(t.id, 'mouseout', function() {
11025 					if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
11026 						DOM.removeClass(t.id, 'mceSplitButtonHover');
11027 				});
11028 			}
11029 		},
11030 
11031 		destroy : function() {
11032 			this.parent();
11033 
11034 			Event.clear(this.id + '_action');
11035 			Event.clear(this.id + '_open');
11036 			Event.clear(this.id);
11037 		}
11038 	});
11039 })(tinymce);
11040 
11041 (function(tinymce) {
11042 	var DOM = tinymce.DOM, Event = tinymce.dom.Event, is = tinymce.is, each = tinymce.each;
11043 
11044 	tinymce.create('tinymce.ui.ColorSplitButton:tinymce.ui.SplitButton', {
11045 		ColorSplitButton : function(id, s, ed) {
11046 			var t = this;
11047 
11048 			t.parent(id, s, ed);
11049 
11050 			t.settings = s = tinymce.extend({
11051 				colors : '000000,993300,333300,003300,003366,000080,333399,333333,800000,FF6600,808000,008000,008080,0000FF,666699,808080,FF0000,FF9900,99CC00,339966,33CCCC,3366FF,800080,999999,FF00FF,FFCC00,FFFF00,00FF00,00FFFF,00CCFF,993366,C0C0C0,FF99CC,FFCC99,FFFF99,CCFFCC,CCFFFF,99CCFF,CC99FF,FFFFFF',
11052 				grid_width : 8,
11053 				default_color : '#888888'
11054 			}, t.settings);
11055 
11056 			t.onShowMenu = new tinymce.util.Dispatcher(t);
11057 
11058 			t.onHideMenu = new tinymce.util.Dispatcher(t);
11059 
11060 			t.value = s.default_color;
11061 		},
11062 
11063 		showMenu : function() {
11064 			var t = this, r, p, e, p2;
11065 
11066 			if (t.isDisabled())
11067 				return;
11068 
11069 			if (!t.isMenuRendered) {
11070 				t.renderMenu();
11071 				t.isMenuRendered = true;
11072 			}
11073 
11074 			if (t.isMenuVisible)
11075 				return t.hideMenu();
11076 
11077 			e = DOM.get(t.id);
11078 			DOM.show(t.id + '_menu');
11079 			DOM.addClass(e, 'mceSplitButtonSelected');
11080 			p2 = DOM.getPos(e);
11081 			DOM.setStyles(t.id + '_menu', {
11082 				left : p2.x,
11083 				top : p2.y + e.firstChild.clientHeight,
11084 				zIndex : 200000
11085 			});
11086 			e = 0;
11087 
11088 			Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
11089 			t.onShowMenu.dispatch(t);
11090 
11091 			if (t._focused) {
11092 				t._keyHandler = Event.add(t.id + '_menu', 'keydown', function(e) {
11093 					if (e.keyCode == 27)
11094 						t.hideMenu();
11095 				});
11096 
11097 				DOM.select('a', t.id + '_menu')[0].focus(); // Select first link
11098 			}
11099 
11100 			t.keyboardNav = new tinymce.ui.KeyboardNavigation({
11101 				root: t.id + '_menu',
11102 				items: DOM.select('a', t.id + '_menu'),
11103 				onCancel: function() {
11104 					t.hideMenu();
11105 					t.focus();
11106 				}
11107 			});
11108 
11109 			t.keyboardNav.focus();
11110 			t.isMenuVisible = 1;
11111 		},
11112 
11113 		hideMenu : function(e) {
11114 			var t = this;
11115 
11116 			if (t.isMenuVisible) {
11117 				// Prevent double toogles by canceling the mouse click event to the button
11118 				if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id + '_open';}))
11119 					return;
11120 
11121 				if (!e || !DOM.getParent(e.target, '.mceSplitButtonMenu')) {
11122 					DOM.removeClass(t.id, 'mceSplitButtonSelected');
11123 					Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
11124 					Event.remove(t.id + '_menu', 'keydown', t._keyHandler);
11125 					DOM.hide(t.id + '_menu');
11126 				}
11127 
11128 				t.isMenuVisible = 0;
11129 				t.onHideMenu.dispatch();
11130 				t.keyboardNav.destroy();
11131 			}
11132 		},
11133 
11134 		renderMenu : function() {
11135 			var t = this, m, i = 0, s = t.settings, n, tb, tr, w, context;
11136 
11137 			w = DOM.add(s.menu_container, 'div', {role: 'listbox', id : t.id + '_menu', 'class' : s.menu_class + ' ' + s['class'], style : 'position:absolute;left:0;top:-1000px;'});
11138 			m = DOM.add(w, 'div', {'class' : s['class'] + ' mceSplitButtonMenu'});
11139 			DOM.add(m, 'span', {'class' : 'mceMenuLine'});
11140 
11141 			n = DOM.add(m, 'table', {role: 'presentation', 'class' : 'mceColorSplitMenu'});
11142 			tb = DOM.add(n, 'tbody');
11143 
11144 			// Generate color grid
11145 			i = 0;
11146 			each(is(s.colors, 'array') ? s.colors : s.colors.split(','), function(c) {
11147 				c = c.replace(/^#/, '');
11148 
11149 				if (!i--) {
11150 					tr = DOM.add(tb, 'tr');
11151 					i = s.grid_width - 1;
11152 				}
11153 
11154 				n = DOM.add(tr, 'td');
11155 				var settings = {
11156 					href : 'javascript:;',
11157 					style : {
11158 						backgroundColor : '#' + c
11159 					},
11160 					'title': t.editor.getLang('colors.' + c, c),
11161 					'data-mce-color' : '#' + c
11162 				};
11163 
11164 				// adding a proper ARIA role = button causes JAWS to read things incorrectly on IE.
11165 				if (!tinymce.isIE ) {
11166 					settings.role = 'option';
11167 				}
11168 
11169 				n = DOM.add(n, 'a', settings);
11170 
11171 				if (t.editor.forcedHighContrastMode) {
11172 					n = DOM.add(n, 'canvas', { width: 16, height: 16, 'aria-hidden': 'true' });
11173 					if (n.getContext && (context = n.getContext("2d"))) {
11174 						context.fillStyle = '#' + c;
11175 						context.fillRect(0, 0, 16, 16);
11176 					} else {
11177 						// No point leaving a canvas element around if it's not supported for drawing on anyway.
11178 						DOM.remove(n);
11179 					}
11180 				}
11181 			});
11182 
11183 			if (s.more_colors_func) {
11184 				n = DOM.add(tb, 'tr');
11185 				n = DOM.add(n, 'td', {colspan : s.grid_width, 'class' : 'mceMoreColors'});
11186 				n = DOM.add(n, 'a', {role: 'option', id : t.id + '_more', href : 'javascript:;', onclick : 'return false;', 'class' : 'mceMoreColors'}, s.more_colors_title);
11187 
11188 				Event.add(n, 'click', function(e) {
11189 					s.more_colors_func.call(s.more_colors_scope || this);
11190 					return Event.cancel(e); // Cancel to fix onbeforeunload problem
11191 				});
11192 			}
11193 
11194 			DOM.addClass(m, 'mceColorSplitMenu');
11195 
11196 			// Prevent IE from scrolling and hindering click to occur #4019
11197 			Event.add(t.id + '_menu', 'mousedown', function(e) {return Event.cancel(e);});
11198 
11199 			Event.add(t.id + '_menu', 'click', function(e) {
11200 				var c;
11201 
11202 				e = DOM.getParent(e.target, 'a', tb);
11203 
11204 				if (e && e.nodeName.toLowerCase() == 'a' && (c = e.getAttribute('data-mce-color')))
11205 					t.setColor(c);
11206 
11207 				return false; // Prevent IE auto save warning
11208 			});
11209 
11210 			return w;
11211 		},
11212 
11213 		setColor : function(c) {
11214 			this.displayColor(c);
11215 			this.hideMenu();
11216 			this.settings.onselect(c);
11217 		},
11218 		
11219 		displayColor : function(c) {
11220 			var t = this;
11221 
11222 			DOM.setStyle(t.id + '_preview', 'backgroundColor', c);
11223 
11224 			t.value = c;
11225 		},
11226 
11227 		postRender : function() {
11228 			var t = this, id = t.id;
11229 
11230 			t.parent();
11231 			DOM.add(id + '_action', 'div', {id : id + '_preview', 'class' : 'mceColorPreview'});
11232 			DOM.setStyle(t.id + '_preview', 'backgroundColor', t.value);
11233 		},
11234 
11235 		destroy : function() {
11236 			var self = this;
11237 
11238 			self.parent();
11239 
11240 			Event.clear(self.id + '_menu');
11241 			Event.clear(self.id + '_more');
11242 			DOM.remove(self.id + '_menu');
11243 
11244 			if (self.keyboardNav) {
11245 				self.keyboardNav.destroy();
11246 			}
11247 		}
11248 	});
11249 })(tinymce);
11250 
11251 (function(tinymce) {
11252 // Shorten class names
11253 var dom = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event;
11254 tinymce.create('tinymce.ui.ToolbarGroup:tinymce.ui.Container', {
11255 	renderHTML : function() {
11256 		var t = this, h = [], controls = t.controls, each = tinymce.each, settings = t.settings;
11257 
11258 		h.push('<div id="' + t.id + '" role="group" aria-labelledby="' + t.id + '_voice">');
11259 		//TODO: ACC test this out - adding a role = application for getting the landmarks working well.
11260 		h.push("<span role='application'>");
11261 		h.push('<span id="' + t.id + '_voice" class="mceVoiceLabel" style="display:none;">' + dom.encode(settings.name) + '</span>');
11262 		each(controls, function(toolbar) {
11263 			h.push(toolbar.renderHTML());
11264 		});
11265 		h.push("</span>");
11266 		h.push('</div>');
11267 
11268 		return h.join('');
11269 	},
11270 	
11271 	focus : function() {
11272 		var t = this;
11273 		dom.get(t.id).focus();
11274 	},
11275 	
11276 	postRender : function() {
11277 		var t = this, items = [];
11278 
11279 		each(t.controls, function(toolbar) {
11280 			each (toolbar.controls, function(control) {
11281 				if (control.id) {
11282 					items.push(control);
11283 				}
11284 			});
11285 		});
11286 
11287 		t.keyNav = new tinymce.ui.KeyboardNavigation({
11288 			root: t.id,
11289 			items: items,
11290 			onCancel: function() {
11291 				//Move focus if webkit so that navigation back will read the item.
11292 				if (tinymce.isWebKit) {
11293 					dom.get(t.editor.id+"_ifr").focus();
11294 				}
11295 				t.editor.focus();
11296 			},
11297 			excludeFromTabOrder: !t.settings.tab_focus_toolbar
11298 		});
11299 	},
11300 	
11301 	destroy : function() {
11302 		var self = this;
11303 
11304 		self.parent();
11305 		self.keyNav.destroy();
11306 		Event.clear(self.id);
11307 	}
11308 });
11309 })(tinymce);
11310 
11311 (function(tinymce) {
11312 // Shorten class names
11313 var dom = tinymce.DOM, each = tinymce.each;
11314 tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
11315 	renderHTML : function() {
11316 		var t = this, h = '', c, co, s = t.settings, i, pr, nx, cl;
11317 
11318 		cl = t.controls;
11319 		for (i=0; i<cl.length; i++) {
11320 			// Get current control, prev control, next control and if the control is a list box or not
11321 			co = cl[i];
11322 			pr = cl[i - 1];
11323 			nx = cl[i + 1];
11324 
11325 			// Add toolbar start
11326 			if (i === 0) {
11327 				c = 'mceToolbarStart';
11328 
11329 				if (co.Button)
11330 					c += ' mceToolbarStartButton';
11331 				else if (co.SplitButton)
11332 					c += ' mceToolbarStartSplitButton';
11333 				else if (co.ListBox)
11334 					c += ' mceToolbarStartListBox';
11335 
11336 				h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
11337 			}
11338 
11339 			// Add toolbar end before list box and after the previous button
11340 			// This is to fix the o2k7 editor skins
11341 			if (pr && co.ListBox) {
11342 				if (pr.Button || pr.SplitButton)
11343 					h += dom.createHTML('td', {'class' : 'mceToolbarEnd'}, dom.createHTML('span', null, '<!-- IE -->'));
11344 			}
11345 
11346 			// Render control HTML
11347 
11348 			// IE 8 quick fix, needed to propertly generate a hit area for anchors
11349 			if (dom.stdMode)
11350 				h += '<td style="position: relative">' + co.renderHTML() + '</td>';
11351 			else
11352 				h += '<td>' + co.renderHTML() + '</td>';
11353 
11354 			// Add toolbar start after list box and before the next button
11355 			// This is to fix the o2k7 editor skins
11356 			if (nx && co.ListBox) {
11357 				if (nx.Button || nx.SplitButton)
11358 					h += dom.createHTML('td', {'class' : 'mceToolbarStart'}, dom.createHTML('span', null, '<!-- IE -->'));
11359 			}
11360 		}
11361 
11362 		c = 'mceToolbarEnd';
11363 
11364 		if (co.Button)
11365 			c += ' mceToolbarEndButton';
11366 		else if (co.SplitButton)
11367 			c += ' mceToolbarEndSplitButton';
11368 		else if (co.ListBox)
11369 			c += ' mceToolbarEndListBox';
11370 
11371 		h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
11372 
11373 		return dom.createHTML('table', {id : t.id, 'class' : 'mceToolbar' + (s['class'] ? ' ' + s['class'] : ''), cellpadding : '0', cellspacing : '0', align : t.settings.align || '', role: 'presentation', tabindex: '-1'}, '<tbody><tr>' + h + '</tr></tbody>');
11374 	}
11375 });
11376 })(tinymce);
11377 
11378 (function(tinymce) {
11379 	var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each;
11380 
11381 	tinymce.create('tinymce.AddOnManager', {
11382 		AddOnManager : function() {
11383 			var self = this;
11384 
11385 			self.items = [];
11386 			self.urls = {};
11387 			self.lookup = {};
11388 			self.onAdd = new Dispatcher(self);
11389 		},
11390 
11391 		get : function(n) {
11392 			if (this.lookup[n]) {
11393 				return this.lookup[n].instance;
11394 			} else {
11395 				return undefined;
11396 			}
11397 		},
11398 
11399 		dependencies : function(n) {
11400 			var result;
11401 			if (this.lookup[n]) {
11402 				result = this.lookup[n].dependencies;
11403 			}
11404 			return result || [];
11405 		},
11406 
11407 		requireLangPack : function(n) {
11408 			var s = tinymce.settings;
11409 
11410 			if (s && s.language && s.language_load !== false)
11411 				tinymce.ScriptLoader.add(this.urls[n] + '/langs/' + s.language + '.js');
11412 		},
11413 
11414 		add : function(id, o, dependencies) {
11415 			this.items.push(o);
11416 			this.lookup[id] = {instance:o, dependencies:dependencies};
11417 			this.onAdd.dispatch(this, id, o);
11418 
11419 			return o;
11420 		},
11421 		createUrl: function(baseUrl, dep) {
11422 			if (typeof dep === "object") {
11423 				return dep
11424 			} else {
11425 				return {prefix: baseUrl.prefix, resource: dep, suffix: baseUrl.suffix};
11426 			}
11427 		},
11428 
11429 		addComponents: function(pluginName, scripts) {
11430 			var pluginUrl = this.urls[pluginName];
11431 			tinymce.each(scripts, function(script){
11432 				tinymce.ScriptLoader.add(pluginUrl+"/"+script);	
11433 			});
11434 		},
11435 
11436 		load : function(n, u, cb, s) {
11437 			var t = this, url = u;
11438 
11439 			function loadDependencies() {
11440 				var dependencies = t.dependencies(n);
11441 				tinymce.each(dependencies, function(dep) {
11442 					var newUrl = t.createUrl(u, dep);
11443 					t.load(newUrl.resource, newUrl, undefined, undefined);
11444 				});
11445 				if (cb) {
11446 					if (s) {
11447 						cb.call(s);
11448 					} else {
11449 						cb.call(tinymce.ScriptLoader);
11450 					}
11451 				}
11452 			}
11453 
11454 			if (t.urls[n])
11455 				return;
11456 			if (typeof u === "object")
11457 				url = u.prefix + u.resource + u.suffix;
11458 
11459 			if (url.indexOf('/') !== 0 && url.indexOf('://') == -1)
11460 				url = tinymce.baseURL + '/' + url;
11461 
11462 			t.urls[n] = url.substring(0, url.lastIndexOf('/'));
11463 
11464 			if (t.lookup[n]) {
11465 				loadDependencies();
11466 			} else {
11467 				tinymce.ScriptLoader.add(url, loadDependencies, s);
11468 			}
11469 		}
11470 	});
11471 
11472 	// Create plugin and theme managers
11473 	tinymce.PluginManager = new tinymce.AddOnManager();
11474 	tinymce.ThemeManager = new tinymce.AddOnManager();
11475 }(tinymce));
11476 
11477 (function(tinymce) {
11478 	// Shorten names
11479 	var each = tinymce.each, extend = tinymce.extend,
11480 		DOM = tinymce.DOM, Event = tinymce.dom.Event,
11481 		ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
11482 		explode = tinymce.explode,
11483 		Dispatcher = tinymce.util.Dispatcher, undef, instanceCounter = 0;
11484 
11485 	// Setup some URLs where the editor API is located and where the document is
11486 	tinymce.documentBaseURL = window.location.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, '');
11487 	if (!/[\/\\]$/.test(tinymce.documentBaseURL))
11488 		tinymce.documentBaseURL += '/';
11489 
11490 	tinymce.baseURL = new tinymce.util.URI(tinymce.documentBaseURL).toAbsolute(tinymce.baseURL);
11491 
11492 	tinymce.baseURI = new tinymce.util.URI(tinymce.baseURL);
11493 
11494 	// Add before unload listener
11495 	// This was required since IE was leaking memory if you added and removed beforeunload listeners
11496 	// with attachEvent/detatchEvent so this only adds one listener and instances can the attach to the onBeforeUnload event
11497 	tinymce.onBeforeUnload = new Dispatcher(tinymce);
11498 
11499 	// Must be on window or IE will leak if the editor is placed in frame or iframe
11500 	Event.add(window, 'beforeunload', function(e) {
11501 		tinymce.onBeforeUnload.dispatch(tinymce, e);
11502 	});
11503 
11504 	tinymce.onAddEditor = new Dispatcher(tinymce);
11505 
11506 	tinymce.onRemoveEditor = new Dispatcher(tinymce);
11507 
11508 	tinymce.EditorManager = extend(tinymce, {
11509 		editors : [],
11510 
11511 		i18n : {},
11512 
11513 		activeEditor : null,
11514 
11515 		init : function(s) {
11516 			var t = this, pl, sl = tinymce.ScriptLoader, e, el = [], ed;
11517 
11518 			function createId(elm) {
11519 				var id = elm.id;
11520 	
11521 				// Use element id, or unique name or generate a unique id
11522 				if (!id) {
11523 					id = elm.name;
11524 	
11525 					if (id && !DOM.get(id)) {
11526 						id = elm.name;
11527 					} else {
11528 						// Generate unique name
11529 						id = DOM.uniqueId();
11530 					}
11531 
11532 					elm.setAttribute('id', id);
11533 				}
11534 
11535 				return id;
11536 			};
11537 
11538 			function execCallback(se, n, s) {
11539 				var f = se[n];
11540 
11541 				if (!f)
11542 					return;
11543 
11544 				if (tinymce.is(f, 'string')) {
11545 					s = f.replace(/\.\w+$/, '');
11546 					s = s ? tinymce.resolve(s) : 0;
11547 					f = tinymce.resolve(f);
11548 				}
11549 
11550 				return f.apply(s || this, Array.prototype.slice.call(arguments, 2));
11551 			};
11552 
11553 			function hasClass(n, c) {
11554 				return c.constructor === RegExp ? c.test(n.className) : DOM.hasClass(n, c);
11555 			};
11556 
11557 			t.settings = s;
11558 
11559 			// Legacy call
11560 			Event.bind(window, 'ready', function() {
11561 				var l, co;
11562 
11563 				execCallback(s, 'onpageload');
11564 
11565 				switch (s.mode) {
11566 					case "exact":
11567 						l = s.elements || '';
11568 
11569 						if(l.length > 0) {
11570 							each(explode(l), function(v) {
11571 								if (DOM.get(v)) {
11572 									ed = new tinymce.Editor(v, s);
11573 									el.push(ed);
11574 									ed.render(1);
11575 								} else {
11576 									each(document.forms, function(f) {
11577 										each(f.elements, function(e) {
11578 											if (e.name === v) {
11579 												v = 'mce_editor_' + instanceCounter++;
11580 												DOM.setAttrib(e, 'id', v);
11581 
11582 												ed = new tinymce.Editor(v, s);
11583 												el.push(ed);
11584 												ed.render(1);
11585 											}
11586 										});
11587 									});
11588 								}
11589 							});
11590 						}
11591 						break;
11592 
11593 					case "textareas":
11594 					case "specific_textareas":
11595 						each(DOM.select('textarea'), function(elm) {
11596 							if (s.editor_deselector && hasClass(elm, s.editor_deselector))
11597 								return;
11598 
11599 							if (!s.editor_selector || hasClass(elm, s.editor_selector)) {
11600 								ed = new tinymce.Editor(createId(elm), s);
11601 								el.push(ed);
11602 								ed.render(1);
11603 							}
11604 						});
11605 						break;
11606 					
11607 					default:
11608 						if (s.types) {
11609 							// Process type specific selector
11610 							each(s.types, function(type) {
11611 								each(DOM.select(type.selector), function(elm) {
11612 									var editor = new tinymce.Editor(createId(elm), tinymce.extend({}, s, type));
11613 									el.push(editor);
11614 									editor.render(1);
11615 								});
11616 							});
11617 						} else if (s.selector) {
11618 							// Process global selector
11619 							each(DOM.select(s.selector), function(elm) {
11620 								var editor = new tinymce.Editor(createId(elm), s);
11621 								el.push(editor);
11622 								editor.render(1);
11623 							});
11624 						}
11625 				}
11626 
11627 				// Call onInit when all editors are initialized
11628 				if (s.oninit) {
11629 					l = co = 0;
11630 
11631 					each(el, function(ed) {
11632 						co++;
11633 
11634 						if (!ed.initialized) {
11635 							// Wait for it
11636 							ed.onInit.add(function() {
11637 								l++;
11638 
11639 								// All done
11640 								if (l == co)
11641 									execCallback(s, 'oninit');
11642 							});
11643 						} else
11644 							l++;
11645 
11646 						// All done
11647 						if (l == co)
11648 							execCallback(s, 'oninit');					
11649 					});
11650 				}
11651 			});
11652 		},
11653 
11654 		get : function(id) {
11655 			if (id === undef)
11656 				return this.editors;
11657 
11658 			return this.editors[id];
11659 		},
11660 
11661 		getInstanceById : function(id) {
11662 			return this.get(id);
11663 		},
11664 
11665 		add : function(editor) {
11666 			var self = this, editors = self.editors;
11667 
11668 			// Add named and index editor instance
11669 			editors[editor.id] = editor;
11670 			editors.push(editor);
11671 
11672 			self._setActive(editor);
11673 			self.onAddEditor.dispatch(self, editor);
11674 
11675 
11676 			// Patch the tinymce.Editor instance with jQuery adapter logic
11677 			if (tinymce.adapter)
11678 				tinymce.adapter.patchEditor(editor);
11679 
11680 
11681 			return editor;
11682 		},
11683 
11684 		remove : function(editor) {
11685 			var t = this, i, editors = t.editors;
11686 
11687 			// Not in the collection
11688 			if (!editors[editor.id])
11689 				return null;
11690 
11691 			delete editors[editor.id];
11692 
11693 			for (i = 0; i < editors.length; i++) {
11694 				if (editors[i] == editor) {
11695 					editors.splice(i, 1);
11696 					break;
11697 				}
11698 			}
11699 
11700 			// Select another editor since the active one was removed
11701 			if (t.activeEditor == editor)
11702 				t._setActive(editors[0]);
11703 
11704 			editor.destroy();
11705 			t.onRemoveEditor.dispatch(t, editor);
11706 
11707 			return editor;
11708 		},
11709 
11710 		execCommand : function(c, u, v) {
11711 			var t = this, ed = t.get(v), w;
11712 
11713 			function clr() {
11714 				ed.destroy();
11715 				w.detachEvent('onunload', clr);
11716 				w = w.tinyMCE = w.tinymce = null; // IE leak
11717 			};
11718 
11719 			// Manager commands
11720 			switch (c) {
11721 				case "mceFocus":
11722 					ed.focus();
11723 					return true;
11724 
11725 				case "mceAddEditor":
11726 				case "mceAddControl":
11727 					if (!t.get(v))
11728 						new tinymce.Editor(v, t.settings).render();
11729 
11730 					return true;
11731 
11732 				case "mceAddFrameControl":
11733 					w = v.window;
11734 
11735 					// Add tinyMCE global instance and tinymce namespace to specified window
11736 					w.tinyMCE = tinyMCE;
11737 					w.tinymce = tinymce;
11738 
11739 					tinymce.DOM.doc = w.document;
11740 					tinymce.DOM.win = w;
11741 
11742 					ed = new tinymce.Editor(v.element_id, v);
11743 					ed.render();
11744 
11745 					// Fix IE memory leaks
11746 					if (tinymce.isIE) {
11747 						w.attachEvent('onunload', clr);
11748 					}
11749 
11750 					v.page_window = null;
11751 
11752 					return true;
11753 
11754 				case "mceRemoveEditor":
11755 				case "mceRemoveControl":
11756 					if (ed)
11757 						ed.remove();
11758 
11759 					return true;
11760 
11761 				case 'mceToggleEditor':
11762 					if (!ed) {
11763 						t.execCommand('mceAddControl', 0, v);
11764 						return true;
11765 					}
11766 
11767 					if (ed.isHidden())
11768 						ed.show();
11769 					else
11770 						ed.hide();
11771 
11772 					return true;
11773 			}
11774 
11775 			// Run command on active editor
11776 			if (t.activeEditor)
11777 				return t.activeEditor.execCommand(c, u, v);
11778 
11779 			return false;
11780 		},
11781 
11782 		execInstanceCommand : function(id, c, u, v) {
11783 			var ed = this.get(id);
11784 
11785 			if (ed)
11786 				return ed.execCommand(c, u, v);
11787 
11788 			return false;
11789 		},
11790 
11791 		triggerSave : function() {
11792 			each(this.editors, function(e) {
11793 				e.save();
11794 			});
11795 		},
11796 
11797 		addI18n : function(p, o) {
11798 			var lo, i18n = this.i18n;
11799 
11800 			if (!tinymce.is(p, 'string')) {
11801 				each(p, function(o, lc) {
11802 					each(o, function(o, g) {
11803 						each(o, function(o, k) {
11804 							if (g === 'common')
11805 								i18n[lc + '.' + k] = o;
11806 							else
11807 								i18n[lc + '.' + g + '.' + k] = o;
11808 						});
11809 					});
11810 				});
11811 			} else {
11812 				each(o, function(o, k) {
11813 					i18n[p + '.' + k] = o;
11814 				});
11815 			}
11816 		},
11817 
11818 		// Private methods
11819 
11820 		_setActive : function(editor) {
11821 			this.selectedInstance = this.activeEditor = editor;
11822 		}
11823 	});
11824 })(tinymce);
11825 
11826 (function(tinymce) {
11827 	// Shorten these names
11828 	var DOM = tinymce.DOM, Event = tinymce.dom.Event, extend = tinymce.extend,
11829 		each = tinymce.each, isGecko = tinymce.isGecko,
11830 		isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, is = tinymce.is,
11831 		ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
11832 		explode = tinymce.explode;
11833 
11834 	tinymce.create('tinymce.Editor', {
11835 		Editor : function(id, settings) {
11836 			var self = this, TRUE = true;
11837 
11838 			self.settings = settings = extend({
11839 				id : id,
11840 				language : 'en',
11841 				theme : 'advanced',
11842 				skin : 'default',
11843 				delta_width : 0,
11844 				delta_height : 0,
11845 				popup_css : '',
11846 				plugins : '',
11847 				document_base_url : tinymce.documentBaseURL,
11848 				add_form_submit_trigger : TRUE,
11849 				submit_patch : TRUE,
11850 				add_unload_trigger : TRUE,
11851 				convert_urls : TRUE,
11852 				relative_urls : TRUE,
11853 				remove_script_host : TRUE,
11854 				table_inline_editing : false,
11855 				object_resizing : TRUE,
11856 				accessibility_focus : TRUE,
11857 				doctype : tinymce.isIE6 ? '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">' : '<!DOCTYPE>', // Use old doctype on IE 6 to avoid horizontal scroll
11858 				visual : TRUE,
11859 				font_size_style_values : 'xx-small,x-small,small,medium,large,x-large,xx-large',
11860 				font_size_legacy_values : 'xx-small,small,medium,large,x-large,xx-large,300%', // See: http://www.w3.org/TR/CSS2/fonts.html#propdef-font-size
11861 				apply_source_formatting : TRUE,
11862 				directionality : 'ltr',
11863 				forced_root_block : 'p',
11864 				hidden_input : TRUE,
11865 				padd_empty_editor : TRUE,
11866 				render_ui : TRUE,
11867 				indentation : '30px',
11868 				fix_table_elements : TRUE,
11869 				inline_styles : TRUE,
11870 				convert_fonts_to_spans : TRUE,
11871 				indent : 'simple',
11872 				indent_before : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist',
11873 				indent_after : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist',
11874 				validate : TRUE,
11875 				entity_encoding : 'named',
11876 				url_converter : self.convertURL,
11877 				url_converter_scope : self,
11878 				ie7_compat : TRUE
11879 			}, settings);
11880 
11881 			self.id = self.editorId = id;
11882 
11883 			self.isNotDirty = false;
11884 
11885 			self.plugins = {};
11886 
11887 			self.documentBaseURI = new tinymce.util.URI(settings.document_base_url || tinymce.documentBaseURL, {
11888 				base_uri : tinyMCE.baseURI
11889 			});
11890 
11891 			self.baseURI = tinymce.baseURI;
11892 
11893 			self.contentCSS = [];
11894 
11895 			self.contentStyles = [];
11896 
11897 			// Creates all events like onClick, onSetContent etc see Editor.Events.js for the actual logic
11898 			self.setupEvents();
11899 
11900 			// Internal command handler objects
11901 			self.execCommands = {};
11902 			self.queryStateCommands = {};
11903 			self.queryValueCommands = {};
11904 
11905 			// Call setup
11906 			self.execCallback('setup', self);
11907 		},
11908 
11909 		render : function(nst) {
11910 			var t = this, s = t.settings, id = t.id, sl = tinymce.ScriptLoader;
11911 
11912 			// Page is not loaded yet, wait for it
11913 			if (!Event.domLoaded) {
11914 				Event.add(window, 'ready', function() {
11915 					t.render();
11916 				});
11917 				return;
11918 			}
11919 
11920 			tinyMCE.settings = s;
11921 
11922 			// Element not found, then skip initialization
11923 			if (!t.getElement())
11924 				return;
11925 
11926 			// Is a iPad/iPhone and not on iOS5, then skip initialization. We need to sniff 
11927 			// here since the browser says it has contentEditable support but there is no visible caret.
11928 			if (tinymce.isIDevice && !tinymce.isIOS5)
11929 				return;
11930 
11931 			// Add hidden input for non input elements inside form elements
11932 			if (!/TEXTAREA|INPUT/i.test(t.getElement().nodeName) && s.hidden_input && DOM.getParent(id, 'form'))
11933 				DOM.insertAfter(DOM.create('input', {type : 'hidden', name : id}), id);
11934 
11935 			// Hide target element early to prevent content flashing
11936 			if (!s.content_editable) {
11937 				t.orgVisibility = t.getElement().style.visibility;
11938 				t.getElement().style.visibility = 'hidden';
11939 			}
11940 
11941 			if (tinymce.WindowManager)
11942 				t.windowManager = new tinymce.WindowManager(t);
11943 
11944 			if (s.encoding == 'xml') {
11945 				t.onGetContent.add(function(ed, o) {
11946 					if (o.save)
11947 						o.content = DOM.encode(o.content);
11948 				});
11949 			}
11950 
11951 			if (s.add_form_submit_trigger) {
11952 				t.onSubmit.addToTop(function() {
11953 					if (t.initialized) {
11954 						t.save();
11955 						t.isNotDirty = 1;
11956 					}
11957 				});
11958 			}
11959 
11960 			if (s.add_unload_trigger) {
11961 				t._beforeUnload = tinyMCE.onBeforeUnload.add(function() {
11962 					if (t.initialized && !t.destroyed && !t.isHidden())
11963 						t.save({format : 'raw', no_events : true});
11964 				});
11965 			}
11966 
11967 			tinymce.addUnload(t.destroy, t);
11968 
11969 			if (s.submit_patch) {
11970 				t.onBeforeRenderUI.add(function() {
11971 					var n = t.getElement().form;
11972 
11973 					if (!n)
11974 						return;
11975 
11976 					// Already patched
11977 					if (n._mceOldSubmit)
11978 						return;
11979 
11980 					// Check page uses id="submit" or name="submit" for it's submit button
11981 					if (!n.submit.nodeType && !n.submit.length) {
11982 						t.formElement = n;
11983 						n._mceOldSubmit = n.submit;
11984 						n.submit = function() {
11985 							// Save all instances
11986 							tinymce.triggerSave();
11987 							t.isNotDirty = 1;
11988 
11989 							return t.formElement._mceOldSubmit(t.formElement);
11990 						};
11991 					}
11992 
11993 					n = null;
11994 				});
11995 			}
11996 
11997 			// Load scripts
11998 			function loadScripts() {
11999 				if (s.language && s.language_load !== false)
12000 					sl.add(tinymce.baseURL + '/langs/' + s.language + '.js');
12001 
12002 				if (s.theme && typeof s.theme != "function" && s.theme.charAt(0) != '-' && !ThemeManager.urls[s.theme])
12003 					ThemeManager.load(s.theme, 'themes/' + s.theme + '/editor_template' + tinymce.suffix + '.js');
12004 
12005 				each(explode(s.plugins), function(p) {
12006 					if (p &&!PluginManager.urls[p]) {
12007 						if (p.charAt(0) == '-') {
12008 							p = p.substr(1, p.length);
12009 							var dependencies = PluginManager.dependencies(p);
12010 							each(dependencies, function(dep) {
12011 								var defaultSettings = {prefix:'plugins/', resource: dep, suffix:'/editor_plugin' + tinymce.suffix + '.js'};
12012 								dep = PluginManager.createUrl(defaultSettings, dep);
12013 								PluginManager.load(dep.resource, dep);
12014 							});
12015 						} else {
12016 							// Skip safari plugin, since it is removed as of 3.3b1
12017 							if (p == 'safari') {
12018 								return;
12019 							}
12020 							PluginManager.load(p, {prefix:'plugins/', resource: p, suffix:'/editor_plugin' + tinymce.suffix + '.js'});
12021 						}
12022 					}
12023 				});
12024 
12025 				// Init when que is loaded
12026 				sl.loadQueue(function() {
12027 					if (!t.removed)
12028 						t.init();
12029 				});
12030 			};
12031 
12032 			loadScripts();
12033 		},
12034 
12035 		init : function() {
12036 			var n, t = this, s = t.settings, w, h, mh, e = t.getElement(), o, ti, u, bi, bc, re, i, initializedPlugins = [];
12037 
12038 			tinymce.add(t);
12039 
12040 			s.aria_label = s.aria_label || DOM.getAttrib(e, 'aria-label', t.getLang('aria.rich_text_area'));
12041 
12042 			if (s.theme) {
12043 				if (typeof s.theme != "function") {
12044 					s.theme = s.theme.replace(/-/, '');
12045 					o = ThemeManager.get(s.theme);
12046 					t.theme = new o();
12047 
12048 					if (t.theme.init)
12049 						t.theme.init(t, ThemeManager.urls[s.theme] || tinymce.documentBaseURL.replace(/\/$/, ''));
12050 				} else {
12051 					t.theme = s.theme;
12052 				}
12053 			}
12054 
12055 			function initPlugin(p) {
12056 				var c = PluginManager.get(p), u = PluginManager.urls[p] || tinymce.documentBaseURL.replace(/\/$/, ''), po;
12057 				if (c && tinymce.inArray(initializedPlugins,p) === -1) {
12058 					each(PluginManager.dependencies(p), function(dep){
12059 						initPlugin(dep);
12060 					});
12061 					po = new c(t, u);
12062 
12063 					t.plugins[p] = po;
12064 
12065 					if (po.init) {
12066 						po.init(t, u);
12067 						initializedPlugins.push(p);
12068 					}
12069 				}
12070 			}
12071 			
12072 			// Create all plugins
12073 			each(explode(s.plugins.replace(/\-/g, '')), initPlugin);
12074 
12075 			// Setup popup CSS path(s)
12076 			if (s.popup_css !== false) {
12077 				if (s.popup_css)
12078 					s.popup_css = t.documentBaseURI.toAbsolute(s.popup_css);
12079 				else
12080 					s.popup_css = t.baseURI.toAbsolute("themes/" + s.theme + "/skins/" + s.skin + "/dialog.css");
12081 			}
12082 
12083 			if (s.popup_css_add)
12084 				s.popup_css += ',' + t.documentBaseURI.toAbsolute(s.popup_css_add);
12085 
12086 			t.controlManager = new tinymce.ControlManager(t);
12087 
12088 			// Enables users to override the control factory
12089 			t.onBeforeRenderUI.dispatch(t, t.controlManager);
12090 
12091 			// Measure box
12092 			if (s.render_ui && t.theme) {
12093 				t.orgDisplay = e.style.display;
12094 
12095 				if (typeof s.theme != "function") {
12096 					w = s.width || e.style.width || e.offsetWidth;
12097 					h = s.height || e.style.height || e.offsetHeight;
12098 					mh = s.min_height || 100;
12099 					re = /^[0-9\.]+(|px)$/i;
12100 
12101 					if (re.test('' + w))
12102 						w = Math.max(parseInt(w, 10) + (o.deltaWidth || 0), 100);
12103 
12104 					if (re.test('' + h))
12105 						h = Math.max(parseInt(h, 10) + (o.deltaHeight || 0), mh);
12106 
12107 					// Render UI
12108 					o = t.theme.renderUI({
12109 						targetNode : e,
12110 						width : w,
12111 						height : h,
12112 						deltaWidth : s.delta_width,
12113 						deltaHeight : s.delta_height
12114 					});
12115 
12116 					// Resize editor
12117 					DOM.setStyles(o.sizeContainer || o.editorContainer, {
12118 						width : w,
12119 						height : h
12120 					});
12121 
12122 					h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : '');
12123 					if (h < mh)
12124 						h = mh;
12125 				} else {
12126 					o = s.theme(t, e);
12127 
12128 					// Convert element type to id:s
12129 					if (o.editorContainer.nodeType) {
12130 						o.editorContainer = o.editorContainer.id = o.editorContainer.id || t.id + "_parent";
12131 					}
12132 
12133 					// Convert element type to id:s
12134 					if (o.iframeContainer.nodeType) {
12135 						o.iframeContainer = o.iframeContainer.id = o.iframeContainer.id || t.id + "_iframecontainer";
12136 					}
12137 
12138 					// Use specified iframe height or the targets offsetHeight
12139 					h = o.iframeHeight || e.offsetHeight;
12140 
12141 					// Store away the selection when it's changed to it can be restored later with a editor.focus() call
12142 					if (isIE) {
12143 						t.onInit.add(function(ed) {
12144 							ed.dom.bind(ed.getBody(), 'beforedeactivate keydown', function() {
12145 								ed.lastIERng = ed.selection.getRng();
12146 							});
12147 						});
12148 					}
12149 				}
12150 
12151 				t.editorContainer = o.editorContainer;
12152 			}
12153 
12154 			// Load specified content CSS last
12155 			if (s.content_css) {
12156 				each(explode(s.content_css), function(u) {
12157 					t.contentCSS.push(t.documentBaseURI.toAbsolute(u));
12158 				});
12159 			}
12160 
12161 			// Content editable mode ends here
12162 			if (s.content_editable) {
12163 				e = n = o = null; // Fix IE leak
12164 				return t.initContentBody();
12165 			}
12166 
12167 			// User specified a document.domain value
12168 			if (document.domain && location.hostname != document.domain)
12169 				tinymce.relaxedDomain = document.domain;
12170 
12171 			t.iframeHTML = s.doctype + '<html><head xmlns="http://www.w3.org/1999/xhtml">';
12172 
12173 			// We only need to override paths if we have to
12174 			// IE has a bug where it remove site absolute urls to relative ones if this is specified
12175 			if (s.document_base_url != tinymce.documentBaseURL)
12176 				t.iframeHTML += '<base href="' + t.documentBaseURI.getURI() + '" />';
12177 
12178 			// IE8 doesn't support carets behind images setting ie7_compat would force IE8+ to run in IE7 compat mode.
12179 			if (s.ie7_compat)
12180 				t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=7" />';
12181 			else
12182 				t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=edge" />';
12183 
12184 			t.iframeHTML += '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />';
12185 
12186 			// Load the CSS by injecting them into the HTML this will reduce "flicker"
12187 			for (i = 0; i < t.contentCSS.length; i++) {
12188 				t.iframeHTML += '<link type="text/css" rel="stylesheet" href="' + t.contentCSS[i] + '" />';
12189 			}
12190 
12191 			t.contentCSS = [];
12192 
12193 			bi = s.body_id || 'tinymce';
12194 			if (bi.indexOf('=') != -1) {
12195 				bi = t.getParam('body_id', '', 'hash');
12196 				bi = bi[t.id] || bi;
12197 			}
12198 
12199 			bc = s.body_class || '';
12200 			if (bc.indexOf('=') != -1) {
12201 				bc = t.getParam('body_class', '', 'hash');
12202 				bc = bc[t.id] || '';
12203 			}
12204 
12205 			t.iframeHTML += '</head><body id="' + bi + '" class="mceContentBody ' + bc + '" onload="window.parent.tinyMCE.get(\'' + t.id + '\').onLoad.dispatch();"><br></body></html>';
12206 
12207 			// Domain relaxing enabled, then set document domain
12208 			if (tinymce.relaxedDomain && (isIE || (tinymce.isOpera && parseFloat(opera.version()) < 11))) {
12209 				// We need to write the contents here in IE since multiple writes messes up refresh button and back button
12210 				u = 'javascript:(function(){document.open();document.domain="' + document.domain + '";var ed = window.parent.tinyMCE.get("' + t.id + '");document.write(ed.iframeHTML);document.close();ed.initContentBody();})()';
12211 			}
12212 
12213 			// Create iframe
12214 			// TODO: ACC add the appropriate description on this.
12215 			n = DOM.add(o.iframeContainer, 'iframe', { 
12216 				id : t.id + "_ifr",
12217 				src : u || 'javascript:""', // Workaround for HTTPS warning in IE6/7
12218 				frameBorder : '0',
12219 				allowTransparency : "true",
12220 				title : s.aria_label,
12221 				style : {
12222 					width : '100%',
12223 					height : h,
12224 					display : 'block' // Important for Gecko to render the iframe correctly
12225 				}
12226 			});
12227 
12228 			t.contentAreaContainer = o.iframeContainer;
12229 
12230 			if (o.editorContainer) {
12231 				DOM.get(o.editorContainer).style.display = t.orgDisplay;
12232 			}
12233 
12234 			// Restore visibility on target element
12235 			e.style.visibility = t.orgVisibility;
12236 
12237 			DOM.get(t.id).style.display = 'none';
12238 			DOM.setAttrib(t.id, 'aria-hidden', true);
12239 
12240 			if (!tinymce.relaxedDomain || !u)
12241 				t.initContentBody();
12242 
12243 			e = n = o = null; // Cleanup
12244 		},
12245 
12246 		initContentBody : function() {
12247 			var self = this, settings = self.settings, targetElm = DOM.get(self.id), doc = self.getDoc(), html, body, contentCssText;
12248 
12249 			// Setup iframe body
12250 			if ((!isIE || !tinymce.relaxedDomain) && !settings.content_editable) {
12251 				doc.open();
12252 				doc.write(self.iframeHTML);
12253 				doc.close();
12254 
12255 				if (tinymce.relaxedDomain)
12256 					doc.domain = tinymce.relaxedDomain;
12257 			}
12258 
12259 			if (settings.content_editable) {
12260 				DOM.addClass(targetElm, 'mceContentBody');
12261 				self.contentDocument = doc = settings.content_document || document;
12262 				self.contentWindow = settings.content_window || window;
12263 				self.bodyElement = targetElm;
12264 
12265 				// Prevent leak in IE
12266 				settings.content_document = settings.content_window = null;
12267 			}
12268 
12269 			// It will not steal focus while setting contentEditable
12270 			body = self.getBody();
12271 			body.disabled = true;
12272 
12273 			if (!settings.readonly)
12274 				body.contentEditable = self.getParam('content_editable_state', true);
12275 
12276 			body.disabled = false;
12277 
12278 			self.schema = new tinymce.html.Schema(settings);
12279 
12280 			self.dom = new tinymce.dom.DOMUtils(doc, {
12281 				keep_values : true,
12282 				url_converter : self.convertURL,
12283 				url_converter_scope : self,
12284 				hex_colors : settings.force_hex_style_colors,
12285 				class_filter : settings.class_filter,
12286 				update_styles : true,
12287 				root_element : settings.content_editable ? self.id : null,
12288 				schema : self.schema
12289 			});
12290 
12291 			self.parser = new tinymce.html.DomParser(settings, self.schema);
12292 
12293 			// Convert src and href into data-mce-src, data-mce-href and data-mce-style
12294 			self.parser.addAttributeFilter('src,href,style', function(nodes, name) {
12295 				var i = nodes.length, node, dom = self.dom, value, internalName;
12296 
12297 				while (i--) {
12298 					node = nodes[i];
12299 					value = node.attr(name);
12300 					internalName = 'data-mce-' + name;
12301 
12302 					// Add internal attribute if we need to we don't on a refresh of the document
12303 					if (!node.attributes.map[internalName]) {	
12304 						if (name === "style")
12305 							node.attr(internalName, dom.serializeStyle(dom.parseStyle(value), node.name));
12306 						else
12307 							node.attr(internalName, self.convertURL(value, name, node.name));
12308 					}
12309 				}
12310 			});
12311 
12312 			// Keep scripts from executing
12313 			self.parser.addNodeFilter('script', function(nodes, name) {
12314 				var i = nodes.length, node;
12315 
12316 				while (i--) {
12317 					node = nodes[i];
12318 					node.attr('type', 'mce-' + (node.attr('type') || 'text/javascript'));
12319 				}
12320 			});
12321 
12322 			self.parser.addNodeFilter('#cdata', function(nodes, name) {
12323 				var i = nodes.length, node;
12324 
12325 				while (i--) {
12326 					node = nodes[i];
12327 					node.type = 8;
12328 					node.name = '#comment';
12329 					node.value = '[CDATA[' + node.value + ']]';
12330 				}
12331 			});
12332 
12333 			self.parser.addNodeFilter('p,h1,h2,h3,h4,h5,h6,div', function(nodes, name) {
12334 				var i = nodes.length, node, nonEmptyElements = self.schema.getNonEmptyElements();
12335 
12336 				while (i--) {
12337 					node = nodes[i];
12338 
12339 					if (node.isEmpty(nonEmptyElements))
12340 						node.empty().append(new tinymce.html.Node('br', 1)).shortEnded = true;
12341 				}
12342 			});
12343 
12344 			self.serializer = new tinymce.dom.Serializer(settings, self.dom, self.schema);
12345 
12346 			self.selection = new tinymce.dom.Selection(self.dom, self.getWin(), self.serializer, self);
12347 
12348 			self.formatter = new tinymce.Formatter(self);
12349 
12350 			self.undoManager = new tinymce.UndoManager(self);
12351 
12352 			self.forceBlocks = new tinymce.ForceBlocks(self);
12353 			self.enterKey = new tinymce.EnterKey(self);
12354 			self.editorCommands = new tinymce.EditorCommands(self);
12355 
12356 			self.onExecCommand.add(function(editor, command) {
12357 				// Don't refresh the select lists until caret move
12358 				if (!/^(FontName|FontSize)$/.test(command))
12359 					self.nodeChanged();
12360 			});
12361 
12362 			// Pass through
12363 			self.serializer.onPreProcess.add(function(se, o) {
12364 				return self.onPreProcess.dispatch(self, o, se);
12365 			});
12366 
12367 			self.serializer.onPostProcess.add(function(se, o) {
12368 				return self.onPostProcess.dispatch(self, o, se);
12369 			});
12370 
12371 			self.onPreInit.dispatch(self);
12372 
12373 			if (!settings.browser_spellcheck && !settings.gecko_spellcheck)
12374 				doc.body.spellcheck = false;
12375 
12376 			if (!settings.readonly) {
12377 				self.bindNativeEvents();
12378 			}
12379 
12380 			self.controlManager.onPostRender.dispatch(self, self.controlManager);
12381 			self.onPostRender.dispatch(self);
12382 
12383 			self.quirks = tinymce.util.Quirks(self);
12384 
12385 			if (settings.directionality)
12386 				body.dir = settings.directionality;
12387 
12388 			if (settings.nowrap)
12389 				body.style.whiteSpace = "nowrap";
12390 
12391 			if (settings.protect) {
12392 				self.onBeforeSetContent.add(function(ed, o) {
12393 					each(settings.protect, function(pattern) {
12394 						o.content = o.content.replace(pattern, function(str) {
12395 							return '<!--mce:protected ' + escape(str) + '-->';
12396 						});
12397 					});
12398 				});
12399 			}
12400 
12401 			// Add visual aids when new contents is added
12402 			self.onSetContent.add(function() {
12403 				self.addVisual(self.getBody());
12404 			});
12405 
12406 			// Remove empty contents
12407 			if (settings.padd_empty_editor) {
12408 				self.onPostProcess.add(function(ed, o) {
12409 					o.content = o.content.replace(/^(<p[^>]*>( | |\s|\u00a0|)<\/p>[\r\n]*|<br \/>[\r\n]*)$/, '');
12410 				});
12411 			}
12412 
12413 			self.load({initial : true, format : 'html'});
12414 			self.startContent = self.getContent({format : 'raw'});
12415 
12416 			self.initialized = true;
12417 
12418 			self.onInit.dispatch(self);
12419 			self.execCallback('setupcontent_callback', self.id, body, doc);
12420 			self.execCallback('init_instance_callback', self);
12421 			self.focus(true);
12422 			self.nodeChanged({initial : true});
12423 
12424 			// Add editor specific CSS styles
12425 			if (self.contentStyles.length > 0) {
12426 				contentCssText = '';
12427 
12428 				each(self.contentStyles, function(style) {
12429 					contentCssText += style + "\r\n";
12430 				});
12431 
12432 				self.dom.addStyle(contentCssText);
12433 			}
12434 
12435 			// Load specified content CSS last
12436 			each(self.contentCSS, function(url) {
12437 				self.dom.loadCSS(url);
12438 			});
12439 
12440 			// Handle auto focus
12441 			if (settings.auto_focus) {
12442 				setTimeout(function () {
12443 					var ed = tinymce.get(settings.auto_focus);
12444 
12445 					ed.selection.select(ed.getBody(), 1);
12446 					ed.selection.collapse(1);
12447 					ed.getBody().focus();
12448 					ed.getWin().focus();
12449 				}, 100);
12450 			}
12451 
12452 			// Clean up references for IE
12453 			targetElm = doc = body = null;
12454 		},
12455 
12456 		focus : function(skip_focus) {
12457 			var oed, self = this, selection = self.selection, contentEditable = self.settings.content_editable, ieRng, controlElm, doc = self.getDoc(), body;
12458 
12459 			if (!skip_focus) {
12460 				if (self.lastIERng) {
12461 					selection.setRng(self.lastIERng);
12462 				}
12463 
12464 				// Get selected control element
12465 				ieRng = selection.getRng();
12466 				if (ieRng.item) {
12467 					controlElm = ieRng.item(0);
12468 				}
12469 
12470 				self._refreshContentEditable();
12471 
12472 				// Focus the window iframe
12473 				if (!contentEditable) {
12474 					self.getWin().focus();
12475 				}
12476 
12477 				// Focus the body as well since it's contentEditable
12478 				if (tinymce.isGecko || contentEditable) {
12479 					body = self.getBody();
12480 
12481 					// Check for setActive since it doesn't scroll to the element
12482 					if (body.setActive) {
12483 						body.setActive();
12484 					} else {
12485 						body.focus();
12486 					}
12487 
12488 					if (contentEditable) {
12489 						selection.normalize();
12490 					}
12491 				}
12492 
12493 				// Restore selected control element
12494 				// This is needed when for example an image is selected within a
12495 				// layer a call to focus will then remove the control selection
12496 				if (controlElm && controlElm.ownerDocument == doc) {
12497 					ieRng = doc.body.createControlRange();
12498 					ieRng.addElement(controlElm);
12499 					ieRng.select();
12500 				}
12501 			}
12502 
12503 			if (tinymce.activeEditor != self) {
12504 				if ((oed = tinymce.activeEditor) != null)
12505 					oed.onDeactivate.dispatch(oed, self);
12506 
12507 				self.onActivate.dispatch(self, oed);
12508 			}
12509 
12510 			tinymce._setActive(self);
12511 		},
12512 
12513 		execCallback : function(n) {
12514 			var t = this, f = t.settings[n], s;
12515 
12516 			if (!f)
12517 				return;
12518 
12519 			// Look through lookup
12520 			if (t.callbackLookup && (s = t.callbackLookup[n])) {
12521 				f = s.func;
12522 				s = s.scope;
12523 			}
12524 
12525 			if (is(f, 'string')) {
12526 				s = f.replace(/\.\w+$/, '');
12527 				s = s ? tinymce.resolve(s) : 0;
12528 				f = tinymce.resolve(f);
12529 				t.callbackLookup = t.callbackLookup || {};
12530 				t.callbackLookup[n] = {func : f, scope : s};
12531 			}
12532 
12533 			return f.apply(s || t, Array.prototype.slice.call(arguments, 1));
12534 		},
12535 
12536 		translate : function(s) {
12537 			var c = this.settings.language || 'en', i18n = tinymce.i18n;
12538 
12539 			if (!s)
12540 				return '';
12541 
12542 			return i18n[c + '.' + s] || s.replace(/\{\#([^\}]+)\}/g, function(a, b) {
12543 				return i18n[c + '.' + b] || '{#' + b + '}';
12544 			});
12545 		},
12546 
12547 		getLang : function(n, dv) {
12548 			return tinymce.i18n[(this.settings.language || 'en') + '.' + n] || (is(dv) ? dv : '{#' + n + '}');
12549 		},
12550 
12551 		getParam : function(n, dv, ty) {
12552 			var tr = tinymce.trim, v = is(this.settings[n]) ? this.settings[n] : dv, o;
12553 
12554 			if (ty === 'hash') {
12555 				o = {};
12556 
12557 				if (is(v, 'string')) {
12558 					each(v.indexOf('=') > 0 ? v.split(/[;,](?![^=;,]*(?:[;,]|$))/) : v.split(','), function(v) {
12559 						v = v.split('=');
12560 
12561 						if (v.length > 1)
12562 							o[tr(v[0])] = tr(v[1]);
12563 						else
12564 							o[tr(v[0])] = tr(v);
12565 					});
12566 				} else
12567 					o = v;
12568 
12569 				return o;
12570 			}
12571 
12572 			return v;
12573 		},
12574 
12575 		nodeChanged : function(o) {
12576 			var self = this, selection = self.selection, node;
12577 
12578 			// Fix for bug #1896577 it seems that this can not be fired while the editor is loading
12579 			if (self.initialized) {
12580 				o = o || {};
12581 
12582 				// Get start node
12583 				node = selection.getStart() || self.getBody();
12584 				node = isIE && node.ownerDocument != self.getDoc() ? self.getBody() : node; // Fix for IE initial state
12585 
12586 				// Get parents and add them to object
12587 				o.parents = [];
12588 				self.dom.getParent(node, function(node) {
12589 					if (node.nodeName == 'BODY')
12590 						return true;
12591 
12592 					o.parents.push(node);
12593 				});
12594 
12595 				self.onNodeChange.dispatch(
12596 					self,
12597 					o ? o.controlManager || self.controlManager : self.controlManager,
12598 					node,
12599 					selection.isCollapsed(),
12600 					o
12601 				);
12602 			}
12603 		},
12604 
12605 		addButton : function(name, settings) {
12606 			var self = this;
12607 
12608 			self.buttons = self.buttons || {};
12609 			self.buttons[name] = settings;
12610 		},
12611 
12612 		addCommand : function(name, callback, scope) {
12613 			this.execCommands[name] = {func : callback, scope : scope || this};
12614 		},
12615 
12616 		addQueryStateHandler : function(name, callback, scope) {
12617 			this.queryStateCommands[name] = {func : callback, scope : scope || this};
12618 		},
12619 
12620 		addQueryValueHandler : function(name, callback, scope) {
12621 			this.queryValueCommands[name] = {func : callback, scope : scope || this};
12622 		},
12623 
12624 		addShortcut : function(pa, desc, cmd_func, sc) {
12625 			var t = this, c;
12626 
12627 			if (t.settings.custom_shortcuts === false)
12628 				return false;
12629 
12630 			t.shortcuts = t.shortcuts || {};
12631 
12632 			if (is(cmd_func, 'string')) {
12633 				c = cmd_func;
12634 
12635 				cmd_func = function() {
12636 					t.execCommand(c, false, null);
12637 				};
12638 			}
12639 
12640 			if (is(cmd_func, 'object')) {
12641 				c = cmd_func;
12642 
12643 				cmd_func = function() {
12644 					t.execCommand(c[0], c[1], c[2]);
12645 				};
12646 			}
12647 
12648 			each(explode(pa), function(pa) {
12649 				var o = {
12650 					func : cmd_func,
12651 					scope : sc || this,
12652 					desc : t.translate(desc),
12653 					alt : false,
12654 					ctrl : false,
12655 					shift : false
12656 				};
12657 
12658 				each(explode(pa, '+'), function(v) {
12659 					switch (v) {
12660 						case 'alt':
12661 						case 'ctrl':
12662 						case 'shift':
12663 							o[v] = true;
12664 							break;
12665 
12666 						default:
12667 							o.charCode = v.charCodeAt(0);
12668 							o.keyCode = v.toUpperCase().charCodeAt(0);
12669 					}
12670 				});
12671 
12672 				t.shortcuts[(o.ctrl ? 'ctrl' : '') + ',' + (o.alt ? 'alt' : '') + ',' + (o.shift ? 'shift' : '') + ',' + o.keyCode] = o;
12673 			});
12674 
12675 			return true;
12676 		},
12677 
12678 		execCommand : function(cmd, ui, val, a) {
12679 			var t = this, s = 0, o, st;
12680 
12681 			if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint|SelectAll)$/.test(cmd) && (!a || !a.skip_focus))
12682 				t.focus();
12683 
12684 			a = extend({}, a);
12685 			t.onBeforeExecCommand.dispatch(t, cmd, ui, val, a);
12686 			if (a.terminate)
12687 				return false;
12688 
12689 			// Command callback
12690 			if (t.execCallback('execcommand_callback', t.id, t.selection.getNode(), cmd, ui, val)) {
12691 				t.onExecCommand.dispatch(t, cmd, ui, val, a);
12692 				return true;
12693 			}
12694 
12695 			// Registred commands
12696 			if (o = t.execCommands[cmd]) {
12697 				st = o.func.call(o.scope, ui, val);
12698 
12699 				// Fall through on true
12700 				if (st !== true) {
12701 					t.onExecCommand.dispatch(t, cmd, ui, val, a);
12702 					return st;
12703 				}
12704 			}
12705 
12706 			// Plugin commands
12707 			each(t.plugins, function(p) {
12708 				if (p.execCommand && p.execCommand(cmd, ui, val)) {
12709 					t.onExecCommand.dispatch(t, cmd, ui, val, a);
12710 					s = 1;
12711 					return false;
12712 				}
12713 			});
12714 
12715 			if (s)
12716 				return true;
12717 
12718 			// Theme commands
12719 			if (t.theme && t.theme.execCommand && t.theme.execCommand(cmd, ui, val)) {
12720 				t.onExecCommand.dispatch(t, cmd, ui, val, a);
12721 				return true;
12722 			}
12723 
12724 			// Editor commands
12725 			if (t.editorCommands.execCommand(cmd, ui, val)) {
12726 				t.onExecCommand.dispatch(t, cmd, ui, val, a);
12727 				return true;
12728 			}
12729 
12730 			// Browser commands
12731 			t.getDoc().execCommand(cmd, ui, val);
12732 			t.onExecCommand.dispatch(t, cmd, ui, val, a);
12733 		},
12734 
12735 		queryCommandState : function(cmd) {
12736 			var t = this, o, s;
12737 
12738 			// Is hidden then return undefined
12739 			if (t._isHidden())
12740 				return;
12741 
12742 			// Registred commands
12743 			if (o = t.queryStateCommands[cmd]) {
12744 				s = o.func.call(o.scope);
12745 
12746 				// Fall though on true
12747 				if (s !== true)
12748 					return s;
12749 			}
12750 
12751 			// Registred commands
12752 			o = t.editorCommands.queryCommandState(cmd);
12753 			if (o !== -1)
12754 				return o;
12755 
12756 			// Browser commands
12757 			try {
12758 				return this.getDoc().queryCommandState(cmd);
12759 			} catch (ex) {
12760 				// Fails sometimes see bug: 1896577
12761 			}
12762 		},
12763 
12764 		queryCommandValue : function(c) {
12765 			var t = this, o, s;
12766 
12767 			// Is hidden then return undefined
12768 			if (t._isHidden())
12769 				return;
12770 
12771 			// Registred commands
12772 			if (o = t.queryValueCommands[c]) {
12773 				s = o.func.call(o.scope);
12774 
12775 				// Fall though on true
12776 				if (s !== true)
12777 					return s;
12778 			}
12779 
12780 			// Registred commands
12781 			o = t.editorCommands.queryCommandValue(c);
12782 			if (is(o))
12783 				return o;
12784 
12785 			// Browser commands
12786 			try {
12787 				return this.getDoc().queryCommandValue(c);
12788 			} catch (ex) {
12789 				// Fails sometimes see bug: 1896577
12790 			}
12791 		},
12792 
12793 		show : function() {
12794 			var self = this;
12795 
12796 			DOM.show(self.getContainer());
12797 			DOM.hide(self.id);
12798 			self.load();
12799 		},
12800 
12801 		hide : function() {
12802 			var self = this, doc = self.getDoc();
12803 
12804 			// Fixed bug where IE has a blinking cursor left from the editor
12805 			if (isIE && doc)
12806 				doc.execCommand('SelectAll');
12807 
12808 			// We must save before we hide so Safari doesn't crash
12809 			self.save();
12810 			DOM.hide(self.getContainer());
12811 			DOM.setStyle(self.id, 'display', self.orgDisplay);
12812 		},
12813 
12814 		isHidden : function() {
12815 			return !DOM.isHidden(this.id);
12816 		},
12817 
12818 		setProgressState : function(b, ti, o) {
12819 			this.onSetProgressState.dispatch(this, b, ti, o);
12820 
12821 			return b;
12822 		},
12823 
12824 		load : function(o) {
12825 			var t = this, e = t.getElement(), h;
12826 
12827 			if (e) {
12828 				o = o || {};
12829 				o.load = true;
12830 
12831 				// Double encode existing entities in the value
12832 				h = t.setContent(is(e.value) ? e.value : e.innerHTML, o);
12833 				o.element = e;
12834 
12835 				if (!o.no_events)
12836 					t.onLoadContent.dispatch(t, o);
12837 
12838 				o.element = e = null;
12839 
12840 				return h;
12841 			}
12842 		},
12843 
12844 		save : function(o) {
12845 			var t = this, e = t.getElement(), h, f;
12846 
12847 			if (!e || !t.initialized)
12848 				return;
12849 
12850 			o = o || {};
12851 			o.save = true;
12852 
12853 			o.element = e;
12854 			h = o.content = t.getContent(o);
12855 
12856 			if (!o.no_events)
12857 				t.onSaveContent.dispatch(t, o);
12858 
12859 			h = o.content;
12860 
12861 			if (!/TEXTAREA|INPUT/i.test(e.nodeName)) {
12862 				e.innerHTML = h;
12863 
12864 				// Update hidden form element
12865 				if (f = DOM.getParent(t.id, 'form')) {
12866 					each(f.elements, function(e) {
12867 						if (e.name == t.id) {
12868 							e.value = h;
12869 							return false;
12870 						}
12871 					});
12872 				}
12873 			} else
12874 				e.value = h;
12875 
12876 			o.element = e = null;
12877 
12878 			return h;
12879 		},
12880 
12881 		setContent : function(content, args) {
12882 			var self = this, rootNode, body = self.getBody(), forcedRootBlockName;
12883 
12884 			// Setup args object
12885 			args = args || {};
12886 			args.format = args.format || 'html';
12887 			args.set = true;
12888 			args.content = content;
12889 
12890 			// Do preprocessing
12891 			if (!args.no_events)
12892 				self.onBeforeSetContent.dispatch(self, args);
12893 
12894 			content = args.content;
12895 
12896 			// Padd empty content in Gecko and Safari. Commands will otherwise fail on the content
12897 			// It will also be impossible to place the caret in the editor unless there is a BR element present
12898 			if (!tinymce.isIE && (content.length === 0 || /^\s+$/.test(content))) {
12899 				forcedRootBlockName = self.settings.forced_root_block;
12900 				if (forcedRootBlockName)
12901 					content = '<' + forcedRootBlockName + '><br data-mce-bogus="1"></' + forcedRootBlockName + '>';
12902 				else
12903 					content = '<br data-mce-bogus="1">';
12904 
12905 				body.innerHTML = content;
12906 				self.selection.select(body, true);
12907 				self.selection.collapse(true);
12908 				return;
12909 			}
12910 
12911 			// Parse and serialize the html
12912 			if (args.format !== 'raw') {
12913 				content = new tinymce.html.Serializer({}, self.schema).serialize(
12914 					self.parser.parse(content)
12915 				);
12916 			}
12917 
12918 			// Set the new cleaned contents to the editor
12919 			args.content = tinymce.trim(content);
12920 			self.dom.setHTML(body, args.content);
12921 
12922 			// Do post processing
12923 			if (!args.no_events)
12924 				self.onSetContent.dispatch(self, args);
12925 
12926 			// Don't normalize selection if the focused element isn't the body in content editable mode since it will steal focus otherwise
12927 			if (!self.settings.content_editable || document.activeElement === self.getBody()) {
12928 				self.selection.normalize();
12929 			}
12930 
12931 			return args.content;
12932 		},
12933 
12934 		getContent : function(args) {
12935 			var self = this, content;
12936 
12937 			// Setup args object
12938 			args = args || {};
12939 			args.format = args.format || 'html';
12940 			args.get = true;
12941 			args.getInner = true;
12942 
12943 			// Do preprocessing
12944 			if (!args.no_events)
12945 				self.onBeforeGetContent.dispatch(self, args);
12946 
12947 			// Get raw contents or by default the cleaned contents
12948 			if (args.format == 'raw')
12949 				content = self.getBody().innerHTML;
12950 			else
12951 				content = self.serializer.serialize(self.getBody(), args);
12952 
12953 			args.content = tinymce.trim(content);
12954 
12955 			// Do post processing
12956 			if (!args.no_events)
12957 				self.onGetContent.dispatch(self, args);
12958 
12959 			return args.content;
12960 		},
12961 
12962 		isDirty : function() {
12963 			var self = this;
12964 
12965 			return tinymce.trim(self.startContent) != tinymce.trim(self.getContent({format : 'raw', no_events : 1})) && !self.isNotDirty;
12966 		},
12967 
12968 		getContainer : function() {
12969 			var self = this;
12970 
12971 			if (!self.container)
12972 				self.container = DOM.get(self.editorContainer || self.id + '_parent');
12973 
12974 			return self.container;
12975 		},
12976 
12977 		getContentAreaContainer : function() {
12978 			return this.contentAreaContainer;
12979 		},
12980 
12981 		getElement : function() {
12982 			return DOM.get(this.settings.content_element || this.id);
12983 		},
12984 
12985 		getWin : function() {
12986 			var self = this, elm;
12987 
12988 			if (!self.contentWindow) {
12989 				elm = DOM.get(self.id + "_ifr");
12990 
12991 				if (elm)
12992 					self.contentWindow = elm.contentWindow;
12993 			}
12994 
12995 			return self.contentWindow;
12996 		},
12997 
12998 		getDoc : function() {
12999 			var self = this, win;
13000 
13001 			if (!self.contentDocument) {
13002 				win = self.getWin();
13003 
13004 				if (win)
13005 					self.contentDocument = win.document;
13006 			}
13007 
13008 			return self.contentDocument;
13009 		},
13010 
13011 		getBody : function() {
13012 			return this.bodyElement || this.getDoc().body;
13013 		},
13014 
13015 		convertURL : function(url, name, elm) {
13016 			var self = this, settings = self.settings;
13017 
13018 			// Use callback instead
13019 			if (settings.urlconverter_callback)
13020 				return self.execCallback('urlconverter_callback', url, elm, true, name);
13021 
13022 			// Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs
13023 			if (!settings.convert_urls || (elm && elm.nodeName == 'LINK') || url.indexOf('file:') === 0)
13024 				return url;
13025 
13026 			// Convert to relative
13027 			if (settings.relative_urls)
13028 				return self.documentBaseURI.toRelative(url);
13029 
13030 			// Convert to absolute
13031 			url = self.documentBaseURI.toAbsolute(url, settings.remove_script_host);
13032 
13033 			return url;
13034 		},
13035 
13036 		addVisual : function(elm) {
13037 			var self = this, settings = self.settings, dom = self.dom, cls;
13038 
13039 			elm = elm || self.getBody();
13040 
13041 			if (!is(self.hasVisual))
13042 				self.hasVisual = settings.visual;
13043 
13044 			each(dom.select('table,a', elm), function(elm) {
13045 				var value;
13046 
13047 				switch (elm.nodeName) {
13048 					case 'TABLE':
13049 						cls = settings.visual_table_class || 'mceItemTable';
13050 						value = dom.getAttrib(elm, 'border');
13051 
13052 						if (!value || value == '0') {
13053 							if (self.hasVisual)
13054 								dom.addClass(elm, cls);
13055 							else
13056 								dom.removeClass(elm, cls);
13057 						}
13058 
13059 						return;
13060 
13061 					case 'A':
13062 						if (!dom.getAttrib(elm, 'href', false)) {
13063 							value = dom.getAttrib(elm, 'name') || elm.id;
13064 							cls = 'mceItemAnchor';
13065 
13066 							if (value) {
13067 								if (self.hasVisual)
13068 									dom.addClass(elm, cls);
13069 								else
13070 									dom.removeClass(elm, cls);
13071 							}
13072 						}
13073 
13074 						return;
13075 				}
13076 			});
13077 
13078 			self.onVisualAid.dispatch(self, elm, self.hasVisual);
13079 		},
13080 
13081 		remove : function() {
13082 			var self = this, elm = self.getContainer();
13083 
13084 			if (!self.removed) {
13085 				self.removed = 1; // Cancels post remove event execution
13086 				self.hide();
13087 
13088 				// Don't clear the window or document if content editable
13089 				// is enabled since other instances might still be present
13090 				if (!self.settings.content_editable) {
13091 					Event.unbind(self.getWin());
13092 					Event.unbind(self.getDoc());
13093 				}
13094 
13095 				Event.unbind(self.getBody());
13096 				Event.clear(elm);
13097 
13098 				self.execCallback('remove_instance_callback', self);
13099 				self.onRemove.dispatch(self);
13100 
13101 				// Clear all execCommand listeners this is required to avoid errors if the editor was removed inside another command
13102 				self.onExecCommand.listeners = [];
13103 
13104 				tinymce.remove(self);
13105 				DOM.remove(elm);
13106 			}
13107 		},
13108 
13109 		destroy : function(s) {
13110 			var t = this;
13111 
13112 			// One time is enough
13113 			if (t.destroyed)
13114 				return;
13115 
13116 			// We must unbind on Gecko since it would otherwise produce the pesky "attempt to run compile-and-go script on a cleared scope" message
13117 			if (isGecko) {
13118 				Event.unbind(t.getDoc());
13119 				Event.unbind(t.getWin());
13120 				Event.unbind(t.getBody());
13121 			}
13122 
13123 			if (!s) {
13124 				tinymce.removeUnload(t.destroy);
13125 				tinyMCE.onBeforeUnload.remove(t._beforeUnload);
13126 
13127 				// Manual destroy
13128 				if (t.theme && t.theme.destroy)
13129 					t.theme.destroy();
13130 
13131 				// Destroy controls, selection and dom
13132 				t.controlManager.destroy();
13133 				t.selection.destroy();
13134 				t.dom.destroy();
13135 			}
13136 
13137 			if (t.formElement) {
13138 				t.formElement.submit = t.formElement._mceOldSubmit;
13139 				t.formElement._mceOldSubmit = null;
13140 			}
13141 
13142 			t.contentAreaContainer = t.formElement = t.container = t.settings.content_element = t.bodyElement = t.contentDocument = t.contentWindow = null;
13143 
13144 			if (t.selection)
13145 				t.selection = t.selection.win = t.selection.dom = t.selection.dom.doc = null;
13146 
13147 			t.destroyed = 1;
13148 		},
13149 
13150 		// Internal functions
13151 
13152 		_refreshContentEditable : function() {
13153 			var self = this, body, parent;
13154 
13155 			// Check if the editor was hidden and the re-initalize contentEditable mode by removing and adding the body again
13156 			if (self._isHidden()) {
13157 				body = self.getBody();
13158 				parent = body.parentNode;
13159 
13160 				parent.removeChild(body);
13161 				parent.appendChild(body);
13162 
13163 				body.focus();
13164 			}
13165 		},
13166 
13167 		_isHidden : function() {
13168 			var s;
13169 
13170 			if (!isGecko)
13171 				return 0;
13172 
13173 			// Weird, wheres that cursor selection?
13174 			s = this.selection.getSel();
13175 			return (!s || !s.rangeCount || s.rangeCount === 0);
13176 		}
13177 	});
13178 })(tinymce);
13179 (function(tinymce) {
13180 	var each = tinymce.each;
13181 
13182 	tinymce.Editor.prototype.setupEvents = function() {
13183 		var self = this, settings = self.settings;
13184 
13185 		// Add events to the editor
13186 		each([
13187 			'onPreInit',
13188 
13189 			'onBeforeRenderUI',
13190 
13191 			'onPostRender',
13192 
13193 			'onLoad',
13194 
13195 			'onInit',
13196 
13197 			'onRemove',
13198 
13199 			'onActivate',
13200 
13201 			'onDeactivate',
13202 
13203 			'onClick',
13204 
13205 			'onEvent',
13206 
13207 			'onMouseUp',
13208 
13209 			'onMouseDown',
13210 
13211 			'onDblClick',
13212 
13213 			'onKeyDown',
13214 
13215 			'onKeyUp',
13216 
13217 			'onKeyPress',
13218 
13219 			'onContextMenu',
13220 
13221 			'onSubmit',
13222 
13223 			'onReset',
13224 
13225 			'onPaste',
13226 
13227 			'onPreProcess',
13228 
13229 			'onPostProcess',
13230 
13231 			'onBeforeSetContent',
13232 
13233 			'onBeforeGetContent',
13234 
13235 			'onSetContent',
13236 
13237 			'onGetContent',
13238 
13239 			'onLoadContent',
13240 
13241 			'onSaveContent',
13242 
13243 			'onNodeChange',
13244 
13245 			'onChange',
13246 
13247 			'onBeforeExecCommand',
13248 
13249 			'onExecCommand',
13250 
13251 			'onUndo',
13252 
13253 			'onRedo',
13254 
13255 			'onVisualAid',
13256 
13257 			'onSetProgressState',
13258 
13259 			'onSetAttrib'
13260 		], function(name) {
13261 			self[name] = new tinymce.util.Dispatcher(self);
13262 		});
13263 
13264 		// Handle legacy cleanup_callback option
13265 		if (settings.cleanup_callback) {
13266 			self.onBeforeSetContent.add(function(ed, o) {
13267 				o.content = ed.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
13268 			});
13269 
13270 			self.onPreProcess.add(function(ed, o) {
13271 				if (o.set)
13272 					ed.execCallback('cleanup_callback', 'insert_to_editor_dom', o.node, o);
13273 
13274 				if (o.get)
13275 					ed.execCallback('cleanup_callback', 'get_from_editor_dom', o.node, o);
13276 			});
13277 
13278 			self.onPostProcess.add(function(ed, o) {
13279 				if (o.set)
13280 					o.content = ed.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
13281 
13282 				if (o.get)						
13283 					o.content = ed.execCallback('cleanup_callback', 'get_from_editor', o.content, o);
13284 			});
13285 		}
13286 
13287 		// Handle legacy save_callback option
13288 		if (settings.save_callback) {
13289 			self.onGetContent.add(function(ed, o) {
13290 				if (o.save)
13291 					o.content = ed.execCallback('save_callback', ed.id, o.content, ed.getBody());
13292 			});
13293 		}
13294 
13295 		// Handle legacy handle_event_callback option
13296 		if (settings.handle_event_callback) {
13297 			self.onEvent.add(function(ed, e, o) {
13298 				if (self.execCallback('handle_event_callback', e, ed, o) === false) {
13299 					e.preventDefault();
13300 					e.stopPropagation();
13301 				}
13302 			});
13303 		}
13304 
13305 		// Handle legacy handle_node_change_callback option
13306 		if (settings.handle_node_change_callback) {
13307 			self.onNodeChange.add(function(ed, cm, n) {
13308 				ed.execCallback('handle_node_change_callback', ed.id, n, -1, -1, true, ed.selection.isCollapsed());
13309 			});
13310 		}
13311 
13312 		// Handle legacy save_callback option
13313 		if (settings.save_callback) {
13314 			self.onSaveContent.add(function(ed, o) {
13315 				var h = ed.execCallback('save_callback', ed.id, o.content, ed.getBody());
13316 
13317 				if (h)
13318 					o.content = h;
13319 			});
13320 		}
13321 
13322 		// Handle legacy onchange_callback option
13323 		if (settings.onchange_callback) {
13324 			self.onChange.add(function(ed, l) {
13325 				ed.execCallback('onchange_callback', ed, l);
13326 			});
13327 		}
13328 	};
13329 
13330 	tinymce.Editor.prototype.bindNativeEvents = function() {
13331 		// 'focus', 'blur', 'dblclick', 'beforedeactivate', submit, reset
13332 		var self = this, i, settings = self.settings, dom = self.dom, nativeToDispatcherMap;
13333 
13334 		nativeToDispatcherMap = {
13335 			mouseup : 'onMouseUp',
13336 			mousedown : 'onMouseDown',
13337 			click : 'onClick',
13338 			keyup : 'onKeyUp',
13339 			keydown : 'onKeyDown',
13340 			keypress : 'onKeyPress',
13341 			submit : 'onSubmit',
13342 			reset : 'onReset',
13343 			contextmenu : 'onContextMenu',
13344 			dblclick : 'onDblClick',
13345 			paste : 'onPaste' // Doesn't work in all browsers yet
13346 		};
13347 
13348 		// Handler that takes a native event and sends it out to a dispatcher like onKeyDown
13349 		function eventHandler(evt, args) {
13350 			var type = evt.type;
13351 
13352 			// Don't fire events when it's removed
13353 			if (self.removed)
13354 				return;
13355 
13356 			// Sends the native event out to a global dispatcher then to the specific event dispatcher
13357 			if (self.onEvent.dispatch(self, evt, args) !== false) {
13358 				self[nativeToDispatcherMap[evt.fakeType || evt.type]].dispatch(self, evt, args);
13359 			}
13360 		};
13361 
13362 		// Opera doesn't support focus event for contentEditable elements so we need to fake it
13363 		function doOperaFocus(e) {
13364 			self.focus(true);
13365 		};
13366 
13367 		function nodeChanged(ed, e) {
13368 			// Normalize selection for example <b>a</b><i>|a</i> becomes <b>a|</b><i>a</i> except for Ctrl+A since it selects everything
13369 			if (e.keyCode != 65 || !tinymce.VK.metaKeyPressed(e)) {
13370 				self.selection.normalize();
13371 			}
13372 
13373 			self.nodeChanged();
13374 		}
13375 
13376 		// Add DOM events
13377 		each(nativeToDispatcherMap, function(dispatcherName, nativeName) {
13378 			var root = settings.content_editable ? self.getBody() : self.getDoc();
13379 
13380 			switch (nativeName) {
13381 				case 'contextmenu':
13382 					dom.bind(root, nativeName, eventHandler);
13383 					break;
13384 
13385 				case 'paste':
13386 					dom.bind(self.getBody(), nativeName, eventHandler);
13387 					break;
13388 
13389 				case 'submit':
13390 				case 'reset':
13391 					dom.bind(self.getElement().form || tinymce.DOM.getParent(self.id, 'form'), nativeName, eventHandler);
13392 					break;
13393 
13394 				default:
13395 					dom.bind(root, nativeName, eventHandler);
13396 			}
13397 		});
13398 
13399 		// Set the editor as active when focused
13400 		dom.bind(settings.content_editable ? self.getBody() : (tinymce.isGecko ? self.getDoc() : self.getWin()), 'focus', function(e) {
13401 			self.focus(true);
13402 		});
13403 
13404 		if (settings.content_editable && tinymce.isOpera) {
13405 			dom.bind(self.getBody(), 'click', doOperaFocus);
13406 			dom.bind(self.getBody(), 'keydown', doOperaFocus);
13407 		}
13408 
13409 		// Add node change handler
13410 		self.onMouseUp.add(nodeChanged);
13411 
13412 		self.onKeyUp.add(function(ed, e) {
13413 			var keyCode = e.keyCode;
13414 
13415 			if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 13 || keyCode == 45 || keyCode == 46 || keyCode == 8 || (tinymce.isMac && (keyCode == 91 || keyCode == 93)) || e.ctrlKey)
13416 				nodeChanged(ed, e);
13417 		});
13418 
13419 		// Add reset handler
13420 		self.onReset.add(function() {
13421 			self.setContent(self.startContent, {format : 'raw'});
13422 		});
13423 
13424 		// Add shortcuts
13425 		function handleShortcut(e, execute) {
13426 			if (e.altKey || e.ctrlKey || e.metaKey) {
13427 				each(self.shortcuts, function(shortcut) {
13428 					var ctrlState = tinymce.isMac ? e.metaKey : e.ctrlKey;
13429 
13430 					if (shortcut.ctrl != ctrlState || shortcut.alt != e.altKey || shortcut.shift != e.shiftKey)
13431 						return;
13432 
13433 					if (e.keyCode == shortcut.keyCode || (e.charCode && e.charCode == shortcut.charCode)) {
13434 						e.preventDefault();
13435 
13436 						if (execute) {
13437 							shortcut.func.call(shortcut.scope);
13438 						}
13439 
13440 						return true;
13441 					}
13442 				});
13443 			}
13444 		};
13445 
13446 		self.onKeyUp.add(function(ed, e) {
13447 			handleShortcut(e);
13448 		});
13449 
13450 		self.onKeyPress.add(function(ed, e) {
13451 			handleShortcut(e);
13452 		});
13453 
13454 		self.onKeyDown.add(function(ed, e) {
13455 			handleShortcut(e, true);
13456 		});
13457 
13458 		if (tinymce.isOpera) {
13459 			self.onClick.add(function(ed, e) {
13460 				e.preventDefault();
13461 			});
13462 		}
13463 	};
13464 })(tinymce);
13465 (function(tinymce) {
13466 	// Added for compression purposes
13467 	var each = tinymce.each, undef, TRUE = true, FALSE = false;
13468 
13469 	tinymce.EditorCommands = function(editor) {
13470 		var dom = editor.dom,
13471 			selection = editor.selection,
13472 			commands = {state: {}, exec : {}, value : {}},
13473 			settings = editor.settings,
13474 			formatter = editor.formatter,
13475 			bookmark;
13476 
13477 		function execCommand(command, ui, value) {
13478 			var func;
13479 
13480 			command = command.toLowerCase();
13481 			if (func = commands.exec[command]) {
13482 				func(command, ui, value);
13483 				return TRUE;
13484 			}
13485 
13486 			return FALSE;
13487 		};
13488 
13489 		function queryCommandState(command) {
13490 			var func;
13491 
13492 			command = command.toLowerCase();
13493 			if (func = commands.state[command])
13494 				return func(command);
13495 
13496 			return -1;
13497 		};
13498 
13499 		function queryCommandValue(command) {
13500 			var func;
13501 
13502 			command = command.toLowerCase();
13503 			if (func = commands.value[command])
13504 				return func(command);
13505 
13506 			return FALSE;
13507 		};
13508 
13509 		function addCommands(command_list, type) {
13510 			type = type || 'exec';
13511 
13512 			each(command_list, function(callback, command) {
13513 				each(command.toLowerCase().split(','), function(command) {
13514 					commands[type][command] = callback;
13515 				});
13516 			});
13517 		};
13518 
13519 		// Expose public methods
13520 		tinymce.extend(this, {
13521 			execCommand : execCommand,
13522 			queryCommandState : queryCommandState,
13523 			queryCommandValue : queryCommandValue,
13524 			addCommands : addCommands
13525 		});
13526 
13527 		// Private methods
13528 
13529 		function execNativeCommand(command, ui, value) {
13530 			if (ui === undef)
13531 				ui = FALSE;
13532 
13533 			if (value === undef)
13534 				value = null;
13535 
13536 			return editor.getDoc().execCommand(command, ui, value);
13537 		};
13538 
13539 		function isFormatMatch(name) {
13540 			return formatter.match(name);
13541 		};
13542 
13543 		function toggleFormat(name, value) {
13544 			formatter.toggle(name, value ? {value : value} : undef);
13545 		};
13546 
13547 		function storeSelection(type) {
13548 			bookmark = selection.getBookmark(type);
13549 		};
13550 
13551 		function restoreSelection() {
13552 			selection.moveToBookmark(bookmark);
13553 		};
13554 
13555 		// Add execCommand overrides
13556 		addCommands({
13557 			// Ignore these, added for compatibility
13558 			'mceResetDesignMode,mceBeginUndoLevel' : function() {},
13559 
13560 			// Add undo manager logic
13561 			'mceEndUndoLevel,mceAddUndoLevel' : function() {
13562 				editor.undoManager.add();
13563 			},
13564 
13565 			'Cut,Copy,Paste' : function(command) {
13566 				var doc = editor.getDoc(), failed;
13567 
13568 				// Try executing the native command
13569 				try {
13570 					execNativeCommand(command);
13571 				} catch (ex) {
13572 					// Command failed
13573 					failed = TRUE;
13574 				}
13575 
13576 				// Present alert message about clipboard access not being available
13577 				if (failed || !doc.queryCommandSupported(command)) {
13578 					if (tinymce.isGecko) {
13579 						editor.windowManager.confirm(editor.getLang('clipboard_msg'), function(state) {
13580 							if (state)
13581 								open('http://www.mozilla.org/editor/midasdemo/securityprefs.html', '_blank');
13582 						});
13583 					} else
13584 						editor.windowManager.alert(editor.getLang('clipboard_no_support'));
13585 				}
13586 			},
13587 
13588 			// Override unlink command
13589 			unlink : function(command) {
13590 				if (selection.isCollapsed())
13591 					selection.select(selection.getNode());
13592 
13593 				execNativeCommand(command);
13594 				selection.collapse(FALSE);
13595 			},
13596 
13597 			// Override justify commands to use the text formatter engine
13598 			'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
13599 				var align = command.substring(7);
13600 
13601 				// Remove all other alignments first
13602 				each('left,center,right,full'.split(','), function(name) {
13603 					if (align != name)
13604 						formatter.remove('align' + name);
13605 				});
13606 
13607 				toggleFormat('align' + align);
13608 				execCommand('mceRepaint');
13609 			},
13610 
13611 			// Override list commands to fix WebKit bug
13612 			'InsertUnorderedList,InsertOrderedList' : function(command) {
13613 				var listElm, listParent;
13614 
13615 				execNativeCommand(command);
13616 
13617 				// WebKit produces lists within block elements so we need to split them
13618 				// we will replace the native list creation logic to custom logic later on
13619 				// TODO: Remove this when the list creation logic is removed
13620 				listElm = dom.getParent(selection.getNode(), 'ol,ul');
13621 				if (listElm) {
13622 					listParent = listElm.parentNode;
13623 
13624 					// If list is within a text block then split that block
13625 					if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) {
13626 						storeSelection();
13627 						dom.split(listParent, listElm);
13628 						restoreSelection();
13629 					}
13630 				}
13631 			},
13632 
13633 			// Override commands to use the text formatter engine
13634 			'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) {
13635 				toggleFormat(command);
13636 			},
13637 
13638 			// Override commands to use the text formatter engine
13639 			'ForeColor,HiliteColor,FontName' : function(command, ui, value) {
13640 				toggleFormat(command, value);
13641 			},
13642 
13643 			FontSize : function(command, ui, value) {
13644 				var fontClasses, fontSizes;
13645 
13646 				// Convert font size 1-7 to styles
13647 				if (value >= 1 && value <= 7) {
13648 					fontSizes = tinymce.explode(settings.font_size_style_values);
13649 					fontClasses = tinymce.explode(settings.font_size_classes);
13650 
13651 					if (fontClasses)
13652 						value = fontClasses[value - 1] || value;
13653 					else
13654 						value = fontSizes[value - 1] || value;
13655 				}
13656 
13657 				toggleFormat(command, value);
13658 			},
13659 
13660 			RemoveFormat : function(command) {
13661 				formatter.remove(command);
13662 			},
13663 
13664 			mceBlockQuote : function(command) {
13665 				toggleFormat('blockquote');
13666 			},
13667 
13668 			FormatBlock : function(command, ui, value) {
13669 				return toggleFormat(value || 'p');
13670 			},
13671 
13672 			mceCleanup : function() {
13673 				var bookmark = selection.getBookmark();
13674 
13675 				editor.setContent(editor.getContent({cleanup : TRUE}), {cleanup : TRUE});
13676 
13677 				selection.moveToBookmark(bookmark);
13678 			},
13679 
13680 			mceRemoveNode : function(command, ui, value) {
13681 				var node = value || selection.getNode();
13682 
13683 				// Make sure that the body node isn't removed
13684 				if (node != editor.getBody()) {
13685 					storeSelection();
13686 					editor.dom.remove(node, TRUE);
13687 					restoreSelection();
13688 				}
13689 			},
13690 
13691 			mceSelectNodeDepth : function(command, ui, value) {
13692 				var counter = 0;
13693 
13694 				dom.getParent(selection.getNode(), function(node) {
13695 					if (node.nodeType == 1 && counter++ == value) {
13696 						selection.select(node);
13697 						return FALSE;
13698 					}
13699 				}, editor.getBody());
13700 			},
13701 
13702 			mceSelectNode : function(command, ui, value) {
13703 				selection.select(value);
13704 			},
13705 
13706 			mceInsertContent : function(command, ui, value) {
13707 				var parser, serializer, parentNode, rootNode, fragment, args,
13708 					marker, nodeRect, viewPortRect, rng, node, node2, bookmarkHtml, viewportBodyElement;
13709 
13710 				//selection.normalize();
13711 
13712 				// Setup parser and serializer
13713 				parser = editor.parser;
13714 				serializer = new tinymce.html.Serializer({}, editor.schema);
13715 				bookmarkHtml = '<span id="mce_marker" data-mce-type="bookmark">\uFEFF</span>';
13716 
13717 				// Run beforeSetContent handlers on the HTML to be inserted
13718 				args = {content: value, format: 'html'};
13719 				selection.onBeforeSetContent.dispatch(selection, args);
13720 				value = args.content;
13721 
13722 				// Add caret at end of contents if it's missing
13723 				if (value.indexOf('{$caret}') == -1)
13724 					value += '{$caret}';
13725 
13726 				// Replace the caret marker with a span bookmark element
13727 				value = value.replace(/\{\$caret\}/, bookmarkHtml);
13728 
13729 				// Insert node maker where we will insert the new HTML and get it's parent
13730 				if (!selection.isCollapsed())
13731 					editor.getDoc().execCommand('Delete', false, null);
13732 
13733 				parentNode = selection.getNode();
13734 
13735 				// Parse the fragment within the context of the parent node
13736 				args = {context : parentNode.nodeName.toLowerCase()};
13737 				fragment = parser.parse(value, args);
13738 
13739 				// Move the caret to a more suitable location
13740 				node = fragment.lastChild;
13741 				if (node.attr('id') == 'mce_marker') {
13742 					marker = node;
13743 
13744 					for (node = node.prev; node; node = node.walk(true)) {
13745 						if (node.type == 3 || !dom.isBlock(node.name)) {
13746 							node.parent.insert(marker, node, node.name === 'br');
13747 							break;
13748 						}
13749 					}
13750 				}
13751 
13752 				// If parser says valid we can insert the contents into that parent
13753 				if (!args.invalid) {
13754 					value = serializer.serialize(fragment);
13755 
13756 					// Check if parent is empty or only has one BR element then set the innerHTML of that parent
13757 					node = parentNode.firstChild;
13758 					node2 = parentNode.lastChild;
13759 					if (!node || (node === node2 && node.nodeName === 'BR'))
13760 						dom.setHTML(parentNode, value);
13761 					else
13762 						selection.setContent(value);
13763 				} else {
13764 					// If the fragment was invalid within that context then we need
13765 					// to parse and process the parent it's inserted into
13766 
13767 					// Insert bookmark node and get the parent
13768 					selection.setContent(bookmarkHtml);
13769 					parentNode = editor.selection.getNode();
13770 					rootNode = editor.getBody();
13771 
13772 					// Opera will return the document node when selection is in root
13773 					if (parentNode.nodeType == 9)
13774 						parentNode = node = rootNode;
13775 					else
13776 						node = parentNode;
13777 
13778 					// Find the ancestor just before the root element
13779 					while (node !== rootNode) {
13780 						parentNode = node;
13781 						node = node.parentNode;
13782 					}
13783 
13784 					// Get the outer/inner HTML depending on if we are in the root and parser and serialize that
13785 					value = parentNode == rootNode ? rootNode.innerHTML : dom.getOuterHTML(parentNode);
13786 					value = serializer.serialize(
13787 						parser.parse(
13788 							// Need to replace by using a function since $ in the contents would otherwise be a problem
13789 							value.replace(/<span (id="mce_marker"|id=mce_marker).+?<\/span>/i, function() {
13790 								return serializer.serialize(fragment);
13791 							})
13792 						)
13793 					);
13794 
13795 					// Set the inner/outer HTML depending on if we are in the root or not
13796 					if (parentNode == rootNode)
13797 						dom.setHTML(rootNode, value);
13798 					else
13799 						dom.setOuterHTML(parentNode, value);
13800 				}
13801 
13802 				marker = dom.get('mce_marker');
13803 
13804 				// Scroll range into view scrollIntoView on element can't be used since it will scroll the main view port as well
13805 				nodeRect = dom.getRect(marker);
13806 				viewPortRect = dom.getViewPort(editor.getWin());
13807 
13808 				// Check if node is out side the viewport if it is then scroll to it
13809 				if ((nodeRect.y + nodeRect.h > viewPortRect.y + viewPortRect.h || nodeRect.y < viewPortRect.y) ||
13810 					(nodeRect.x > viewPortRect.x + viewPortRect.w || nodeRect.x < viewPortRect.x)) {
13811 					viewportBodyElement = tinymce.isIE ? editor.getDoc().documentElement : editor.getBody();
13812 					viewportBodyElement.scrollLeft = nodeRect.x;
13813 					viewportBodyElement.scrollTop = nodeRect.y - viewPortRect.h + 25;
13814 				}
13815 
13816 				// Move selection before marker and remove it
13817 				rng = dom.createRng();
13818 
13819 				// If previous sibling is a text node set the selection to the end of that node
13820 				node = marker.previousSibling;
13821 				if (node && node.nodeType == 3) {
13822 					rng.setStart(node, node.nodeValue.length);
13823 				} else {
13824 					// If the previous sibling isn't a text node or doesn't exist set the selection before the marker node
13825 					rng.setStartBefore(marker);
13826 					rng.setEndBefore(marker);
13827 				}
13828 
13829 				// Remove the marker node and set the new range
13830 				dom.remove(marker);
13831 				selection.setRng(rng);
13832 
13833 				// Dispatch after event and add any visual elements needed
13834 				selection.onSetContent.dispatch(selection, args);
13835 				editor.addVisual();
13836 			},
13837 
13838 			mceInsertRawHTML : function(command, ui, value) {
13839 				selection.setContent('tiny_mce_marker');
13840 				editor.setContent(editor.getContent().replace(/tiny_mce_marker/g, function() { return value }));
13841 			},
13842 
13843 			mceToggleFormat : function(command, ui, value) {
13844 				toggleFormat(value);
13845 			},
13846 
13847 			mceSetContent : function(command, ui, value) {
13848 				editor.setContent(value);
13849 			},
13850 
13851 			'Indent,Outdent' : function(command) {
13852 				var intentValue, indentUnit, value;
13853 
13854 				// Setup indent level
13855 				intentValue = settings.indentation;
13856 				indentUnit = /[a-z%]+$/i.exec(intentValue);
13857 				intentValue = parseInt(intentValue);
13858 
13859 				if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) {
13860 					// If forced_root_blocks is set to false we don't have a block to indent so lets create a div
13861 					if (!settings.forced_root_block && !dom.getParent(selection.getNode(), dom.isBlock)) {
13862 						formatter.apply('div');
13863 					}
13864 
13865 					each(selection.getSelectedBlocks(), function(element) {
13866 						if (command == 'outdent') {
13867 							value = Math.max(0, parseInt(element.style.paddingLeft || 0) - intentValue);
13868 							dom.setStyle(element, 'paddingLeft', value ? value + indentUnit : '');
13869 						} else
13870 							dom.setStyle(element, 'paddingLeft', (parseInt(element.style.paddingLeft || 0) + intentValue) + indentUnit);
13871 					});
13872 				} else
13873 					execNativeCommand(command);
13874 			},
13875 
13876 			mceRepaint : function() {
13877 				var bookmark;
13878 
13879 				if (tinymce.isGecko) {
13880 					try {
13881 						storeSelection(TRUE);
13882 
13883 						if (selection.getSel())
13884 							selection.getSel().selectAllChildren(editor.getBody());
13885 
13886 						selection.collapse(TRUE);
13887 						restoreSelection();
13888 					} catch (ex) {
13889 						// Ignore
13890 					}
13891 				}
13892 			},
13893 
13894 			mceToggleFormat : function(command, ui, value) {
13895 				formatter.toggle(value);
13896 			},
13897 
13898 			InsertHorizontalRule : function() {
13899 				editor.execCommand('mceInsertContent', false, '<hr />');
13900 			},
13901 
13902 			mceToggleVisualAid : function() {
13903 				editor.hasVisual = !editor.hasVisual;
13904 				editor.addVisual();
13905 			},
13906 
13907 			mceReplaceContent : function(command, ui, value) {
13908 				editor.execCommand('mceInsertContent', false, value.replace(/\{\$selection\}/g, selection.getContent({format : 'text'})));
13909 			},
13910 
13911 			mceInsertLink : function(command, ui, value) {
13912 				var anchor;
13913 
13914 				if (typeof(value) == 'string')
13915 					value = {href : value};
13916 
13917 				anchor = dom.getParent(selection.getNode(), 'a');
13918 
13919 				// Spaces are never valid in URLs and it's a very common mistake for people to make so we fix it here.
13920 				value.href = value.href.replace(' ', '%20');
13921 
13922 				// Remove existing links if there could be child links or that the href isn't specified
13923 				if (!anchor || !value.href) {
13924 					formatter.remove('link');
13925 				}		
13926 
13927 				// Apply new link to selection
13928 				if (value.href) {
13929 					formatter.apply('link', value, anchor);
13930 				}
13931 			},
13932 
13933 			selectAll : function() {
13934 				var root = dom.getRoot(), rng = dom.createRng();
13935 
13936 				rng.setStart(root, 0);
13937 				rng.setEnd(root, root.childNodes.length);
13938 
13939 				editor.selection.setRng(rng);
13940 			}
13941 		});
13942 
13943 		// Add queryCommandState overrides
13944 		addCommands({
13945 			// Override justify commands
13946 			'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
13947 				var name = 'align' + command.substring(7);
13948 				var nodes = selection.isCollapsed() ? [dom.getParent(selection.getNode(), dom.isBlock)] : selection.getSelectedBlocks();
13949 				var matches = tinymce.map(nodes, function(node) {
13950 					return !!formatter.matchNode(node, name);
13951 				});
13952 				return tinymce.inArray(matches, TRUE) !== -1;
13953 			},
13954 
13955 			'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) {
13956 				return isFormatMatch(command);
13957 			},
13958 
13959 			mceBlockQuote : function() {
13960 				return isFormatMatch('blockquote');
13961 			},
13962 
13963 			Outdent : function() {
13964 				var node;
13965 
13966 				if (settings.inline_styles) {
13967 					if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
13968 						return TRUE;
13969 
13970 					if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
13971 						return TRUE;
13972 				}
13973 
13974 				return queryCommandState('InsertUnorderedList') || queryCommandState('InsertOrderedList') || (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE'));
13975 			},
13976 
13977 			'InsertUnorderedList,InsertOrderedList' : function(command) {
13978 				return dom.getParent(selection.getNode(), command == 'insertunorderedlist' ? 'UL' : 'OL');
13979 			}
13980 		}, 'state');
13981 
13982 		// Add queryCommandValue overrides
13983 		addCommands({
13984 			'FontSize,FontName' : function(command) {
13985 				var value = 0, parent;
13986 
13987 				if (parent = dom.getParent(selection.getNode(), 'span')) {
13988 					if (command == 'fontsize')
13989 						value = parent.style.fontSize;
13990 					else
13991 						value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase();
13992 				}
13993 
13994 				return value;
13995 			}
13996 		}, 'value');
13997 
13998 		// Add undo manager logic
13999 		addCommands({
14000 			Undo : function() {
14001 				editor.undoManager.undo();
14002 			},
14003 
14004 			Redo : function() {
14005 				editor.undoManager.redo();
14006 			}
14007 		});
14008 	};
14009 })(tinymce);
14010 
14011 (function(tinymce) {
14012 	var Dispatcher = tinymce.util.Dispatcher;
14013 
14014 	tinymce.UndoManager = function(editor) {
14015 		var self, index = 0, data = [], beforeBookmark, onAdd, onUndo, onRedo;
14016 
14017 		function getContent() {
14018 			// Remove whitespace before/after and remove pure bogus nodes
14019 			return tinymce.trim(editor.getContent({format : 'raw', no_events : 1}).replace(/<span[^>]+data-mce-bogus[^>]+>[\u200B\uFEFF]+<\/span>/g, ''));
14020 		};
14021 
14022 		function addNonTypingUndoLevel() {
14023 			self.typing = false;
14024 			self.add();
14025 		};
14026 
14027 		// Create event instances
14028 		onBeforeAdd = new Dispatcher(self);
14029 		onAdd       = new Dispatcher(self);
14030 		onUndo      = new Dispatcher(self);
14031 		onRedo      = new Dispatcher(self);
14032 
14033 		// Pass though onAdd event from UndoManager to Editor as onChange
14034 		onAdd.add(function(undoman, level) {
14035 			if (undoman.hasUndo())
14036 				return editor.onChange.dispatch(editor, level, undoman);
14037 		});
14038 
14039 		// Pass though onUndo event from UndoManager to Editor
14040 		onUndo.add(function(undoman, level) {
14041 			return editor.onUndo.dispatch(editor, level, undoman);
14042 		});
14043 
14044 		// Pass though onRedo event from UndoManager to Editor
14045 		onRedo.add(function(undoman, level) {
14046 			return editor.onRedo.dispatch(editor, level, undoman);
14047 		});
14048 
14049 		// Add initial undo level when the editor is initialized
14050 		editor.onInit.add(function() {
14051 			self.add();
14052 		});
14053 
14054 		// Get position before an execCommand is processed
14055 		editor.onBeforeExecCommand.add(function(ed, cmd, ui, val, args) {
14056 			if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!args || !args.skip_undo)) {
14057 				self.beforeChange();
14058 			}
14059 		});
14060 
14061 		// Add undo level after an execCommand call was made
14062 		editor.onExecCommand.add(function(ed, cmd, ui, val, args) {
14063 			if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!args || !args.skip_undo)) {
14064 				self.add();
14065 			}
14066 		});
14067 
14068 		// Add undo level on save contents, drag end and blur/focusout
14069 		editor.onSaveContent.add(addNonTypingUndoLevel);
14070 		editor.dom.bind(editor.dom.getRoot(), 'dragend', addNonTypingUndoLevel);
14071 		editor.dom.bind(editor.getDoc(), tinymce.isGecko ? 'blur' : 'focusout', function(e) {
14072 			if (!editor.removed && self.typing) {
14073 				addNonTypingUndoLevel();
14074 			}
14075 		});
14076 
14077 		editor.onKeyUp.add(function(editor, e) {
14078 			var keyCode = e.keyCode;
14079 
14080 			if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45 || keyCode == 13 || e.ctrlKey) {
14081 				addNonTypingUndoLevel();
14082 			}
14083 		});
14084 
14085 		editor.onKeyDown.add(function(editor, e) {
14086 			var keyCode = e.keyCode;
14087 
14088 			// Is caracter positon keys left,right,up,down,home,end,pgdown,pgup,enter
14089 			if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45) {
14090 				if (self.typing) {
14091 					addNonTypingUndoLevel();
14092 				}
14093 
14094 				return;
14095 			}
14096 
14097 			// If key isn't shift,ctrl,alt,capslock,metakey
14098 			if ((keyCode < 16 || keyCode > 20) && keyCode != 224 && keyCode != 91 && !self.typing) {
14099 				self.beforeChange();
14100 				self.typing = true;
14101 				self.add();
14102 			}
14103 		});
14104 
14105 		editor.onMouseDown.add(function(editor, e) {
14106 			if (self.typing) {
14107 				addNonTypingUndoLevel();
14108 			}
14109 		});
14110 
14111 		// Add keyboard shortcuts for undo/redo keys
14112 		editor.addShortcut('ctrl+z', 'undo_desc', 'Undo');
14113 		editor.addShortcut('ctrl+y', 'redo_desc', 'Redo');
14114 
14115 		self = {
14116 			// Explose for debugging reasons
14117 			data : data,
14118 
14119 			typing : false,
14120 			
14121 			onBeforeAdd: onBeforeAdd,
14122 
14123 			onAdd : onAdd,
14124 
14125 			onUndo : onUndo,
14126 
14127 			onRedo : onRedo,
14128 
14129 			beforeChange : function() {
14130 				beforeBookmark = editor.selection.getBookmark(2, true);
14131 			},
14132 
14133 			add : function(level) {
14134 				var i, settings = editor.settings, lastLevel;
14135 
14136 				level = level || {};
14137 				level.content = getContent();
14138 				
14139 				self.onBeforeAdd.dispatch(self, level);
14140 
14141 				// Add undo level if needed
14142 				lastLevel = data[index];
14143 				if (lastLevel && lastLevel.content == level.content)
14144 					return null;
14145 
14146 				// Set before bookmark on previous level
14147 				if (data[index])
14148 					data[index].beforeBookmark = beforeBookmark;
14149 
14150 				// Time to compress
14151 				if (settings.custom_undo_redo_levels) {
14152 					if (data.length > settings.custom_undo_redo_levels) {
14153 						for (i = 0; i < data.length - 1; i++)
14154 							data[i] = data[i + 1];
14155 
14156 						data.length--;
14157 						index = data.length;
14158 					}
14159 				}
14160 
14161 				// Get a non intrusive normalized bookmark
14162 				level.bookmark = editor.selection.getBookmark(2, true);
14163 
14164 				// Crop array if needed
14165 				if (index < data.length - 1)
14166 					data.length = index + 1;
14167 
14168 				data.push(level);
14169 				index = data.length - 1;
14170 
14171 				self.onAdd.dispatch(self, level);
14172 				editor.isNotDirty = 0;
14173 
14174 				return level;
14175 			},
14176 
14177 			undo : function() {
14178 				var level, i;
14179 
14180 				if (self.typing) {
14181 					self.add();
14182 					self.typing = false;
14183 				}
14184 
14185 				if (index > 0) {
14186 					level = data[--index];
14187 
14188 					editor.setContent(level.content, {format : 'raw'});
14189 					editor.selection.moveToBookmark(level.beforeBookmark);
14190 
14191 					self.onUndo.dispatch(self, level);
14192 				}
14193 
14194 				return level;
14195 			},
14196 
14197 			redo : function() {
14198 				var level;
14199 
14200 				if (index < data.length - 1) {
14201 					level = data[++index];
14202 
14203 					editor.setContent(level.content, {format : 'raw'});
14204 					editor.selection.moveToBookmark(level.bookmark);
14205 
14206 					self.onRedo.dispatch(self, level);
14207 				}
14208 
14209 				return level;
14210 			},
14211 
14212 			clear : function() {
14213 				data = [];
14214 				index = 0;
14215 				self.typing = false;
14216 			},
14217 
14218 			hasUndo : function() {
14219 				return index > 0 || this.typing;
14220 			},
14221 
14222 			hasRedo : function() {
14223 				return index < data.length - 1 && !this.typing;
14224 			}
14225 		};
14226 
14227 		return self;
14228 	};
14229 })(tinymce);
14230 
14231 tinymce.ForceBlocks = function(editor) {
14232 	var settings = editor.settings, dom = editor.dom, selection = editor.selection, blockElements = editor.schema.getBlockElements();
14233 
14234 	function addRootBlocks() {
14235 		var node = selection.getStart(), rootNode = editor.getBody(), rng, startContainer, startOffset, endContainer, endOffset, rootBlockNode, tempNode, offset = -0xFFFFFF, wrapped, isInEditorDocument;
14236 
14237 		if (!node || node.nodeType !== 1 || !settings.forced_root_block)
14238 			return;
14239 
14240 		// Check if node is wrapped in block
14241 		while (node && node != rootNode) {
14242 			if (blockElements[node.nodeName])
14243 				return;
14244 
14245 			node = node.parentNode;
14246 		}
14247 
14248 		// Get current selection
14249 		rng = selection.getRng();
14250 		if (rng.setStart) {
14251 			startContainer = rng.startContainer;
14252 			startOffset = rng.startOffset;
14253 			endContainer = rng.endContainer;
14254 			endOffset = rng.endOffset;
14255 		} else {
14256 			// Force control range into text range
14257 			if (rng.item) {
14258 				node = rng.item(0);
14259 				rng = editor.getDoc().body.createTextRange();
14260 				rng.moveToElementText(node);
14261 			}
14262 
14263 			isInEditorDocument = rng.parentElement().ownerDocument === editor.getDoc();
14264 			tmpRng = rng.duplicate();
14265 			tmpRng.collapse(true);
14266 			startOffset = tmpRng.move('character', offset) * -1;
14267 
14268 			if (!tmpRng.collapsed) {
14269 				tmpRng = rng.duplicate();
14270 				tmpRng.collapse(false);
14271 				endOffset = (tmpRng.move('character', offset) * -1) - startOffset;
14272 			}
14273 		}
14274 
14275 		// Wrap non block elements and text nodes
14276 		node = rootNode.firstChild;
14277 		while (node) {
14278 			if (node.nodeType === 3 || (node.nodeType == 1 && !blockElements[node.nodeName])) {
14279 				if (!rootBlockNode) {
14280 					rootBlockNode = dom.create(settings.forced_root_block);
14281 					node.parentNode.insertBefore(rootBlockNode, node);
14282 					wrapped = true;
14283 				}
14284 
14285 				tempNode = node;
14286 				node = node.nextSibling;
14287 				rootBlockNode.appendChild(tempNode);
14288 			} else {
14289 				rootBlockNode = null;
14290 				node = node.nextSibling;
14291 			}
14292 		}
14293 
14294 		if (wrapped) {
14295 			if (rng.setStart) {
14296 				rng.setStart(startContainer, startOffset);
14297 				rng.setEnd(endContainer, endOffset);
14298 				selection.setRng(rng);
14299 			} else {
14300 				// Only select if the previous selection was inside the document to prevent auto focus in quirks mode
14301 				if (isInEditorDocument) {
14302 					try {
14303 						rng = editor.getDoc().body.createTextRange();
14304 						rng.moveToElementText(rootNode);
14305 						rng.collapse(true);
14306 						rng.moveStart('character', startOffset);
14307 
14308 						if (endOffset > 0)
14309 							rng.moveEnd('character', endOffset);
14310 
14311 						rng.select();
14312 					} catch (ex) {
14313 						// Ignore
14314 					}
14315 				}
14316 			}
14317 
14318 			editor.nodeChanged();
14319 		}
14320 	};
14321 
14322 	// Force root blocks
14323 	if (settings.forced_root_block) {
14324 		editor.onKeyUp.add(addRootBlocks);
14325 		editor.onNodeChange.add(addRootBlocks);
14326 	}
14327 };
14328 
14329 (function(tinymce) {
14330 	// Shorten names
14331 	var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, extend = tinymce.extend;
14332 
14333 	tinymce.create('tinymce.ControlManager', {
14334 		ControlManager : function(ed, s) {
14335 			var t = this, i;
14336 
14337 			s = s || {};
14338 			t.editor = ed;
14339 			t.controls = {};
14340 			t.onAdd = new tinymce.util.Dispatcher(t);
14341 			t.onPostRender = new tinymce.util.Dispatcher(t);
14342 			t.prefix = s.prefix || ed.id + '_';
14343 			t._cls = {};
14344 
14345 			t.onPostRender.add(function() {
14346 				each(t.controls, function(c) {
14347 					c.postRender();
14348 				});
14349 			});
14350 		},
14351 
14352 		get : function(id) {
14353 			return this.controls[this.prefix + id] || this.controls[id];
14354 		},
14355 
14356 		setActive : function(id, s) {
14357 			var c = null;
14358 
14359 			if (c = this.get(id))
14360 				c.setActive(s);
14361 
14362 			return c;
14363 		},
14364 
14365 		setDisabled : function(id, s) {
14366 			var c = null;
14367 
14368 			if (c = this.get(id))
14369 				c.setDisabled(s);
14370 
14371 			return c;
14372 		},
14373 
14374 		add : function(c) {
14375 			var t = this;
14376 
14377 			if (c) {
14378 				t.controls[c.id] = c;
14379 				t.onAdd.dispatch(c, t);
14380 			}
14381 
14382 			return c;
14383 		},
14384 
14385 		createControl : function(name) {
14386 			var ctrl, i, l, self = this, editor = self.editor, factories, ctrlName;
14387 
14388 			// Build control factory cache
14389 			if (!self.controlFactories) {
14390 				self.controlFactories = [];
14391 				each(editor.plugins, function(plugin) {
14392 					if (plugin.createControl) {
14393 						self.controlFactories.push(plugin);
14394 					}
14395 				});
14396 			}
14397 
14398 			// Create controls by asking cached factories
14399 			factories = self.controlFactories;
14400 			for (i = 0, l = factories.length; i < l; i++) {
14401 				ctrl = factories[i].createControl(name, self);
14402 
14403 				if (ctrl) {
14404 					return self.add(ctrl);
14405 				}
14406 			}
14407 
14408 			// Create sepearator
14409 			if (name === "|" || name === "separator") {
14410 				return self.createSeparator();
14411 			}
14412 
14413 			// Create control from button collection
14414 			if (editor.buttons && (ctrl = editor.buttons[name])) {
14415 				return self.createButton(name, ctrl);
14416 			}
14417 
14418 			return self.add(ctrl);
14419 		},
14420 
14421 		createDropMenu : function(id, s, cc) {
14422 			var t = this, ed = t.editor, c, bm, v, cls;
14423 
14424 			s = extend({
14425 				'class' : 'mceDropDown',
14426 				constrain : ed.settings.constrain_menus
14427 			}, s);
14428 
14429 			s['class'] = s['class'] + ' ' + ed.getParam('skin') + 'Skin';
14430 			if (v = ed.getParam('skin_variant'))
14431 				s['class'] += ' ' + ed.getParam('skin') + 'Skin' + v.substring(0, 1).toUpperCase() + v.substring(1);
14432 
14433 			s['class'] += ed.settings.directionality == "rtl" ? ' mceRtl' : '';
14434 
14435 			id = t.prefix + id;
14436 			cls = cc || t._cls.dropmenu || tinymce.ui.DropMenu;
14437 			c = t.controls[id] = new cls(id, s);
14438 			c.onAddItem.add(function(c, o) {
14439 				var s = o.settings;
14440 
14441 				s.title = ed.getLang(s.title, s.title);
14442 
14443 				if (!s.onclick) {
14444 					s.onclick = function(v) {
14445 						if (s.cmd)
14446 							ed.execCommand(s.cmd, s.ui || false, s.value);
14447 					};
14448 				}
14449 			});
14450 
14451 			ed.onRemove.add(function() {
14452 				c.destroy();
14453 			});
14454 
14455 			// Fix for bug #1897785, #1898007
14456 			if (tinymce.isIE) {
14457 				c.onShowMenu.add(function() {
14458 					// IE 8 needs focus in order to store away a range with the current collapsed caret location
14459 					ed.focus();
14460 
14461 					bm = ed.selection.getBookmark(1);
14462 				});
14463 
14464 				c.onHideMenu.add(function() {
14465 					if (bm) {
14466 						ed.selection.moveToBookmark(bm);
14467 						bm = 0;
14468 					}
14469 				});
14470 			}
14471 
14472 			return t.add(c);
14473 		},
14474 
14475 		createListBox : function(id, s, cc) {
14476 			var t = this, ed = t.editor, cmd, c, cls;
14477 
14478 			if (t.get(id))
14479 				return null;
14480 
14481 			s.title = ed.translate(s.title);
14482 			s.scope = s.scope || ed;
14483 
14484 			if (!s.onselect) {
14485 				s.onselect = function(v) {
14486 					ed.execCommand(s.cmd, s.ui || false, v || s.value);
14487 				};
14488 			}
14489 
14490 			s = extend({
14491 				title : s.title,
14492 				'class' : 'mce_' + id,
14493 				scope : s.scope,
14494 				control_manager : t
14495 			}, s);
14496 
14497 			id = t.prefix + id;
14498 
14499 
14500 			function useNativeListForAccessibility(ed) {
14501 				return ed.settings.use_accessible_selects && !tinymce.isGecko
14502 			}
14503 
14504 			if (ed.settings.use_native_selects || useNativeListForAccessibility(ed))
14505 				c = new tinymce.ui.NativeListBox(id, s);
14506 			else {
14507 				cls = cc || t._cls.listbox || tinymce.ui.ListBox;
14508 				c = new cls(id, s, ed);
14509 			}
14510 
14511 			t.controls[id] = c;
14512 
14513 			// Fix focus problem in Safari
14514 			if (tinymce.isWebKit) {
14515 				c.onPostRender.add(function(c, n) {
14516 					// Store bookmark on mousedown
14517 					Event.add(n, 'mousedown', function() {
14518 						ed.bookmark = ed.selection.getBookmark(1);
14519 					});
14520 
14521 					// Restore on focus, since it might be lost
14522 					Event.add(n, 'focus', function() {
14523 						ed.selection.moveToBookmark(ed.bookmark);
14524 						ed.bookmark = null;
14525 					});
14526 				});
14527 			}
14528 
14529 			if (c.hideMenu)
14530 				ed.onMouseDown.add(c.hideMenu, c);
14531 
14532 			return t.add(c);
14533 		},
14534 
14535 		createButton : function(id, s, cc) {
14536 			var t = this, ed = t.editor, o, c, cls;
14537 
14538 			if (t.get(id))
14539 				return null;
14540 
14541 			s.title = ed.translate(s.title);
14542 			s.label = ed.translate(s.label);
14543 			s.scope = s.scope || ed;
14544 
14545 			if (!s.onclick && !s.menu_button) {
14546 				s.onclick = function() {
14547 					ed.execCommand(s.cmd, s.ui || false, s.value);
14548 				};
14549 			}
14550 
14551 			s = extend({
14552 				title : s.title,
14553 				'class' : 'mce_' + id,
14554 				unavailable_prefix : ed.getLang('unavailable', ''),
14555 				scope : s.scope,
14556 				control_manager : t
14557 			}, s);
14558 
14559 			id = t.prefix + id;
14560 
14561 			if (s.menu_button) {
14562 				cls = cc || t._cls.menubutton || tinymce.ui.MenuButton;
14563 				c = new cls(id, s, ed);
14564 				ed.onMouseDown.add(c.hideMenu, c);
14565 			} else {
14566 				cls = t._cls.button || tinymce.ui.Button;
14567 				c = new cls(id, s, ed);
14568 			}
14569 
14570 			return t.add(c);
14571 		},
14572 
14573 		createMenuButton : function(id, s, cc) {
14574 			s = s || {};
14575 			s.menu_button = 1;
14576 
14577 			return this.createButton(id, s, cc);
14578 		},
14579 
14580 		createSplitButton : function(id, s, cc) {
14581 			var t = this, ed = t.editor, cmd, c, cls;
14582 
14583 			if (t.get(id))
14584 				return null;
14585 
14586 			s.title = ed.translate(s.title);
14587 			s.scope = s.scope || ed;
14588 
14589 			if (!s.onclick) {
14590 				s.onclick = function(v) {
14591 					ed.execCommand(s.cmd, s.ui || false, v || s.value);
14592 				};
14593 			}
14594 
14595 			if (!s.onselect) {
14596 				s.onselect = function(v) {
14597 					ed.execCommand(s.cmd, s.ui || false, v || s.value);
14598 				};
14599 			}
14600 
14601 			s = extend({
14602 				title : s.title,
14603 				'class' : 'mce_' + id,
14604 				scope : s.scope,
14605 				control_manager : t
14606 			}, s);
14607 
14608 			id = t.prefix + id;
14609 			cls = cc || t._cls.splitbutton || tinymce.ui.SplitButton;
14610 			c = t.add(new cls(id, s, ed));
14611 			ed.onMouseDown.add(c.hideMenu, c);
14612 
14613 			return c;
14614 		},
14615 
14616 		createColorSplitButton : function(id, s, cc) {
14617 			var t = this, ed = t.editor, cmd, c, cls, bm;
14618 
14619 			if (t.get(id))
14620 				return null;
14621 
14622 			s.title = ed.translate(s.title);
14623 			s.scope = s.scope || ed;
14624 
14625 			if (!s.onclick) {
14626 				s.onclick = function(v) {
14627 					if (tinymce.isIE)
14628 						bm = ed.selection.getBookmark(1);
14629 
14630 					ed.execCommand(s.cmd, s.ui || false, v || s.value);
14631 				};
14632 			}
14633 
14634 			if (!s.onselect) {
14635 				s.onselect = function(v) {
14636 					ed.execCommand(s.cmd, s.ui || false, v || s.value);
14637 				};
14638 			}
14639 
14640 			s = extend({
14641 				title : s.title,
14642 				'class' : 'mce_' + id,
14643 				'menu_class' : ed.getParam('skin') + 'Skin',
14644 				scope : s.scope,
14645 				more_colors_title : ed.getLang('more_colors')
14646 			}, s);
14647 
14648 			id = t.prefix + id;
14649 			cls = cc || t._cls.colorsplitbutton || tinymce.ui.ColorSplitButton;
14650 			c = new cls(id, s, ed);
14651 			ed.onMouseDown.add(c.hideMenu, c);
14652 
14653 			// Remove the menu element when the editor is removed
14654 			ed.onRemove.add(function() {
14655 				c.destroy();
14656 			});
14657 
14658 			// Fix for bug #1897785, #1898007
14659 			if (tinymce.isIE) {
14660 				c.onShowMenu.add(function() {
14661 					// IE 8 needs focus in order to store away a range with the current collapsed caret location
14662 					ed.focus();
14663 					bm = ed.selection.getBookmark(1);
14664 				});
14665 
14666 				c.onHideMenu.add(function() {
14667 					if (bm) {
14668 						ed.selection.moveToBookmark(bm);
14669 						bm = 0;
14670 					}
14671 				});
14672 			}
14673 
14674 			return t.add(c);
14675 		},
14676 
14677 		createToolbar : function(id, s, cc) {
14678 			var c, t = this, cls;
14679 
14680 			id = t.prefix + id;
14681 			cls = cc || t._cls.toolbar || tinymce.ui.Toolbar;
14682 			c = new cls(id, s, t.editor);
14683 
14684 			if (t.get(id))
14685 				return null;
14686 
14687 			return t.add(c);
14688 		},
14689 		
14690 		createToolbarGroup : function(id, s, cc) {
14691 			var c, t = this, cls;
14692 			id = t.prefix + id;
14693 			cls = cc || this._cls.toolbarGroup || tinymce.ui.ToolbarGroup;
14694 			c = new cls(id, s, t.editor);
14695 			
14696 			if (t.get(id))
14697 				return null;
14698 			
14699 			return t.add(c);
14700 		},
14701 
14702 		createSeparator : function(cc) {
14703 			var cls = cc || this._cls.separator || tinymce.ui.Separator;
14704 
14705 			return new cls();
14706 		},
14707 
14708 		setControlType : function(n, c) {
14709 			return this._cls[n.toLowerCase()] = c;
14710 		},
14711 	
14712 		destroy : function() {
14713 			each(this.controls, function(c) {
14714 				c.destroy();
14715 			});
14716 
14717 			this.controls = null;
14718 		}
14719 	});
14720 })(tinymce);
14721 
14722 (function(tinymce) {
14723 	var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isIE = tinymce.isIE, isOpera = tinymce.isOpera;
14724 
14725 	tinymce.create('tinymce.WindowManager', {
14726 		WindowManager : function(ed) {
14727 			var t = this;
14728 
14729 			t.editor = ed;
14730 			t.onOpen = new Dispatcher(t);
14731 			t.onClose = new Dispatcher(t);
14732 			t.params = {};
14733 			t.features = {};
14734 		},
14735 
14736 		open : function(s, p) {
14737 			var t = this, f = '', x, y, mo = t.editor.settings.dialog_type == 'modal', w, sw, sh, vp = tinymce.DOM.getViewPort(), u;
14738 
14739 			// Default some options
14740 			s = s || {};
14741 			p = p || {};
14742 			sw = isOpera ? vp.w : screen.width; // Opera uses windows inside the Opera window
14743 			sh = isOpera ? vp.h : screen.height;
14744 			s.name = s.name || 'mc_' + new Date().getTime();
14745 			s.width = parseInt(s.width || 320);
14746 			s.height = parseInt(s.height || 240);
14747 			s.resizable = true;
14748 			s.left = s.left || parseInt(sw / 2.0) - (s.width / 2.0);
14749 			s.top = s.top || parseInt(sh / 2.0) - (s.height / 2.0);
14750 			p.inline = false;
14751 			p.mce_width = s.width;
14752 			p.mce_height = s.height;
14753 			p.mce_auto_focus = s.auto_focus;
14754 
14755 			if (mo) {
14756 				if (isIE) {
14757 					s.center = true;
14758 					s.help = false;
14759 					s.dialogWidth = s.width + 'px';
14760 					s.dialogHeight = s.height + 'px';
14761 					s.scroll = s.scrollbars || false;
14762 				}
14763 			}
14764 
14765 			// Build features string
14766 			each(s, function(v, k) {
14767 				if (tinymce.is(v, 'boolean'))
14768 					v = v ? 'yes' : 'no';
14769 
14770 				if (!/^(name|url)$/.test(k)) {
14771 					if (isIE && mo)
14772 						f += (f ? ';' : '') + k + ':' + v;
14773 					else
14774 						f += (f ? ',' : '') + k + '=' + v;
14775 				}
14776 			});
14777 
14778 			t.features = s;
14779 			t.params = p;
14780 			t.onOpen.dispatch(t, s, p);
14781 
14782 			u = s.url || s.file;
14783 			u = tinymce._addVer(u);
14784 
14785 			try {
14786 				if (isIE && mo) {
14787 					w = 1;
14788 					window.showModalDialog(u, window, f);
14789 				} else
14790 					w = window.open(u, s.name, f);
14791 			} catch (ex) {
14792 				// Ignore
14793 			}
14794 
14795 			if (!w)
14796 				alert(t.editor.getLang('popup_blocked'));
14797 		},
14798 
14799 		close : function(w) {
14800 			w.close();
14801 			this.onClose.dispatch(this);
14802 		},
14803 
14804 		createInstance : function(cl, a, b, c, d, e) {
14805 			var f = tinymce.resolve(cl);
14806 
14807 			return new f(a, b, c, d, e);
14808 		},
14809 
14810 		confirm : function(t, cb, s, w) {
14811 			w = w || window;
14812 
14813 			cb.call(s || this, w.confirm(this._decode(this.editor.getLang(t, t))));
14814 		},
14815 
14816 		alert : function(tx, cb, s, w) {
14817 			var t = this;
14818 
14819 			w = w || window;
14820 			w.alert(t._decode(t.editor.getLang(tx, tx)));
14821 
14822 			if (cb)
14823 				cb.call(s || t);
14824 		},
14825 
14826 		resizeBy : function(dw, dh, win) {
14827 			win.resizeBy(dw, dh);
14828 		},
14829 
14830 		// Internal functions
14831 
14832 		_decode : function(s) {
14833 			return tinymce.DOM.decode(s).replace(/\\n/g, '\n');
14834 		}
14835 	});
14836 }(tinymce));
14837 (function(tinymce) {
14838 	tinymce.Formatter = function(ed) {
14839 		var formats = {},
14840 			each = tinymce.each,
14841 			dom = ed.dom,
14842 			selection = ed.selection,
14843 			TreeWalker = tinymce.dom.TreeWalker,
14844 			rangeUtils = new tinymce.dom.RangeUtils(dom),
14845 			isValid = ed.schema.isValidChild,
14846 			isBlock = dom.isBlock,
14847 			forcedRootBlock = ed.settings.forced_root_block,
14848 			nodeIndex = dom.nodeIndex,
14849 			INVISIBLE_CHAR = tinymce.isGecko ? '\u200B' : '\uFEFF',
14850 			MCE_ATTR_RE = /^(src|href|style)$/,
14851 			FALSE = false,
14852 			TRUE = true,
14853 			formatChangeData,
14854 			undef,
14855 			getContentEditable = dom.getContentEditable;
14856 
14857 		function isArray(obj) {
14858 			return obj instanceof Array;
14859 		};
14860 
14861 		function getParents(node, selector) {
14862 			return dom.getParents(node, selector, dom.getRoot());
14863 		};
14864 
14865 		function isCaretNode(node) {
14866 			return node.nodeType === 1 && node.id === '_mce_caret';
14867 		};
14868 
14869 		function defaultFormats() {
14870 			register({
14871 				alignleft : [
14872 					{selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'left'}, defaultBlock: 'div'},
14873 					{selector : 'img,table', collapsed : false, styles : {'float' : 'left'}}
14874 				],
14875 
14876 				aligncenter : [
14877 					{selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'center'}, defaultBlock: 'div'},
14878 					{selector : 'img', collapsed : false, styles : {display : 'block', marginLeft : 'auto', marginRight : 'auto'}},
14879 					{selector : 'table', collapsed : false, styles : {marginLeft : 'auto', marginRight : 'auto'}}
14880 				],
14881 
14882 				alignright : [
14883 					{selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'right'}, defaultBlock: 'div'},
14884 					{selector : 'img,table', collapsed : false, styles : {'float' : 'right'}}
14885 				],
14886 
14887 				alignfull : [
14888 					{selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'justify'}, defaultBlock: 'div'}
14889 				],
14890 
14891 				bold : [
14892 					{inline : 'strong', remove : 'all'},
14893 					{inline : 'span', styles : {fontWeight : 'bold'}},
14894 					{inline : 'b', remove : 'all'}
14895 				],
14896 
14897 				italic : [
14898 					{inline : 'em', remove : 'all'},
14899 					{inline : 'span', styles : {fontStyle : 'italic'}},
14900 					{inline : 'i', remove : 'all'}
14901 				],
14902 
14903 				underline : [
14904 					{inline : 'span', styles : {textDecoration : 'underline'}, exact : true},
14905 					{inline : 'u', remove : 'all'}
14906 				],
14907 
14908 				strikethrough : [
14909 					{inline : 'span', styles : {textDecoration : 'line-through'}, exact : true},
14910 					{inline : 'strike', remove : 'all'}
14911 				],
14912 
14913 				forecolor : {inline : 'span', styles : {color : '%value'}, wrap_links : false},
14914 				hilitecolor : {inline : 'span', styles : {backgroundColor : '%value'}, wrap_links : false},
14915 				fontname : {inline : 'span', styles : {fontFamily : '%value'}},
14916 				fontsize : {inline : 'span', styles : {fontSize : '%value'}},
14917 				fontsize_class : {inline : 'span', attributes : {'class' : '%value'}},
14918 				blockquote : {block : 'blockquote', wrapper : 1, remove : 'all'},
14919 				subscript : {inline : 'sub'},
14920 				superscript : {inline : 'sup'},
14921 
14922 				link : {inline : 'a', selector : 'a', remove : 'all', split : true, deep : true,
14923 					onmatch : function(node) {
14924 						return true;
14925 					},
14926 
14927 					onformat : function(elm, fmt, vars) {
14928 						each(vars, function(value, key) {
14929 							dom.setAttrib(elm, key, value);
14930 						});
14931 					}
14932 				},
14933 
14934 				removeformat : [
14935 					{selector : 'b,strong,em,i,font,u,strike', remove : 'all', split : true, expand : false, block_expand : true, deep : true},
14936 					{selector : 'span', attributes : ['style', 'class'], remove : 'empty', split : true, expand : false, deep : true},
14937 					{selector : '*', attributes : ['style', 'class'], split : false, expand : false, deep : true}
14938 				]
14939 			});
14940 
14941 			// Register default block formats
14942 			each('p h1 h2 h3 h4 h5 h6 div address pre div code dt dd samp'.split(/\s/), function(name) {
14943 				register(name, {block : name, remove : 'all'});
14944 			});
14945 
14946 			// Register user defined formats
14947 			register(ed.settings.formats);
14948 		};
14949 
14950 		function addKeyboardShortcuts() {
14951 			// Add some inline shortcuts
14952 			ed.addShortcut('ctrl+b', 'bold_desc', 'Bold');
14953 			ed.addShortcut('ctrl+i', 'italic_desc', 'Italic');
14954 			ed.addShortcut('ctrl+u', 'underline_desc', 'Underline');
14955 
14956 			// BlockFormat shortcuts keys
14957 			for (var i = 1; i <= 6; i++) {
14958 				ed.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]);
14959 			}
14960 
14961 			ed.addShortcut('ctrl+7', '', ['FormatBlock', false, 'p']);
14962 			ed.addShortcut('ctrl+8', '', ['FormatBlock', false, 'div']);
14963 			ed.addShortcut('ctrl+9', '', ['FormatBlock', false, 'address']);
14964 		};
14965 
14966 		// Public functions
14967 
14968 		function get(name) {
14969 			return name ? formats[name] : formats;
14970 		};
14971 
14972 		function register(name, format) {
14973 			if (name) {
14974 				if (typeof(name) !== 'string') {
14975 					each(name, function(format, name) {
14976 						register(name, format);
14977 					});
14978 				} else {
14979 					// Force format into array and add it to internal collection
14980 					format = format.length ? format : [format];
14981 
14982 					each(format, function(format) {
14983 						// Set deep to false by default on selector formats this to avoid removing
14984 						// alignment on images inside paragraphs when alignment is changed on paragraphs
14985 						if (format.deep === undef)
14986 							format.deep = !format.selector;
14987 
14988 						// Default to true
14989 						if (format.split === undef)
14990 							format.split = !format.selector || format.inline;
14991 
14992 						// Default to true
14993 						if (format.remove === undef && format.selector && !format.inline)
14994 							format.remove = 'none';
14995 
14996 						// Mark format as a mixed format inline + block level
14997 						if (format.selector && format.inline) {
14998 							format.mixed = true;
14999 							format.block_expand = true;
15000 						}
15001 
15002 						// Split classes if needed
15003 						if (typeof(format.classes) === 'string')
15004 							format.classes = format.classes.split(/\s+/);
15005 					});
15006 
15007 					formats[name] = format;
15008 				}
15009 			}
15010 		};
15011 
15012 		var getTextDecoration = function(node) {
15013 			var decoration;
15014 
15015 			ed.dom.getParent(node, function(n) {
15016 				decoration = ed.dom.getStyle(n, 'text-decoration');
15017 				return decoration && decoration !== 'none';
15018 			});
15019 
15020 			return decoration;
15021 		};
15022 
15023 		var processUnderlineAndColor = function(node) {
15024 			var textDecoration;
15025 			if (node.nodeType === 1 && node.parentNode && node.parentNode.nodeType === 1) {
15026 				textDecoration = getTextDecoration(node.parentNode);
15027 				if (ed.dom.getStyle(node, 'color') && textDecoration) {
15028 					ed.dom.setStyle(node, 'text-decoration', textDecoration);
15029 				} else if (ed.dom.getStyle(node, 'textdecoration') === textDecoration) {
15030 					ed.dom.setStyle(node, 'text-decoration', null);
15031 				}
15032 			}
15033 		};
15034 
15035 		function apply(name, vars, node) {
15036 			var formatList = get(name), format = formatList[0], bookmark, rng, i, isCollapsed = selection.isCollapsed();
15037 
15038 			function setElementFormat(elm, fmt) {
15039 				fmt = fmt || format;
15040 
15041 				if (elm) {
15042 					if (fmt.onformat) {
15043 						fmt.onformat(elm, fmt, vars, node);
15044 					}
15045 
15046 					each(fmt.styles, function(value, name) {
15047 						dom.setStyle(elm, name, replaceVars(value, vars));
15048 					});
15049 
15050 					each(fmt.attributes, function(value, name) {
15051 						dom.setAttrib(elm, name, replaceVars(value, vars));
15052 					});
15053 
15054 					each(fmt.classes, function(value) {
15055 						value = replaceVars(value, vars);
15056 
15057 						if (!dom.hasClass(elm, value))
15058 							dom.addClass(elm, value);
15059 					});
15060 				}
15061 			};
15062 			function adjustSelectionToVisibleSelection() {
15063 				function findSelectionEnd(start, end) {
15064 					var walker = new TreeWalker(end);
15065 					for (node = walker.current(); node; node = walker.prev()) {
15066 						if (node.childNodes.length > 1 || node == start || node.tagName == 'BR') {
15067 							return node;
15068 						}
15069 					}
15070 				};
15071 
15072 				// Adjust selection so that a end container with a end offset of zero is not included in the selection
15073 				// as this isn't visible to the user.
15074 				var rng = ed.selection.getRng();
15075 				var start = rng.startContainer;
15076 				var end = rng.endContainer;
15077 
15078 				if (start != end && rng.endOffset === 0) {
15079 					var newEnd = findSelectionEnd(start, end);
15080 					var endOffset = newEnd.nodeType == 3 ? newEnd.length : newEnd.childNodes.length;
15081 
15082 					rng.setEnd(newEnd, endOffset);
15083 				}
15084 
15085 				return rng;
15086 			}
15087 			
15088 			function applyStyleToList(node, bookmark, wrapElm, newWrappers, process){
15089 				var nodes = [], listIndex = -1, list, startIndex = -1, endIndex = -1, currentWrapElm;
15090 				
15091 				// find the index of the first child list.
15092 				each(node.childNodes, function(n, index) {
15093 					if (n.nodeName === "UL" || n.nodeName === "OL") {
15094 						listIndex = index;
15095 						list = n;
15096 						return false;
15097 					}
15098 				});
15099 				
15100 				// get the index of the bookmarks
15101 				each(node.childNodes, function(n, index) {
15102 					if (n.nodeName === "SPAN" && dom.getAttrib(n, "data-mce-type") == "bookmark") {
15103 						if (n.id == bookmark.id + "_start") {
15104 							startIndex = index;
15105 						} else if (n.id == bookmark.id + "_end") {
15106 							endIndex = index;
15107 						}
15108 					}
15109 				});
15110 				
15111 				// if the selection spans across an embedded list, or there isn't an embedded list - handle processing normally
15112 				if (listIndex <= 0 || (startIndex < listIndex && endIndex > listIndex)) {
15113 					each(tinymce.grep(node.childNodes), process);
15114 					return 0;
15115 				} else {
15116 					currentWrapElm = dom.clone(wrapElm, FALSE);
15117 
15118 					// create a list of the nodes on the same side of the list as the selection
15119 					each(tinymce.grep(node.childNodes), function(n, index) {
15120 						if ((startIndex < listIndex && index < listIndex) || (startIndex > listIndex && index > listIndex)) {
15121 							nodes.push(n); 
15122 							n.parentNode.removeChild(n);
15123 						}
15124 					});
15125 
15126 					// insert the wrapping element either before or after the list.
15127 					if (startIndex < listIndex) {
15128 						node.insertBefore(currentWrapElm, list);
15129 					} else if (startIndex > listIndex) {
15130 						node.insertBefore(currentWrapElm, list.nextSibling);
15131 					}
15132 					
15133 					// add the new nodes to the list.
15134 					newWrappers.push(currentWrapElm);
15135 
15136 					each(nodes, function(node) {
15137 						currentWrapElm.appendChild(node);
15138 					});
15139 
15140 					return currentWrapElm;
15141 				}
15142 			};
15143 
15144 			function applyRngStyle(rng, bookmark, node_specific) {
15145 				var newWrappers = [], wrapName, wrapElm, contentEditable = true;
15146 
15147 				// Setup wrapper element
15148 				wrapName = format.inline || format.block;
15149 				wrapElm = dom.create(wrapName);
15150 				setElementFormat(wrapElm);
15151 
15152 				rangeUtils.walk(rng, function(nodes) {
15153 					var currentWrapElm;
15154 
15155 					function process(node) {
15156 						var nodeName, parentName, found, hasContentEditableState, lastContentEditable;
15157 
15158 						lastContentEditable = contentEditable;
15159 						nodeName = node.nodeName.toLowerCase();
15160 						parentName = node.parentNode.nodeName.toLowerCase();
15161 
15162 						// Node has a contentEditable value
15163 						if (node.nodeType === 1 && getContentEditable(node)) {
15164 							lastContentEditable = contentEditable;
15165 							contentEditable = getContentEditable(node) === "true";
15166 							hasContentEditableState = true; // We don't want to wrap the container only it's children
15167 						}
15168 
15169 						// Stop wrapping on br elements
15170 						if (isEq(nodeName, 'br')) {
15171 							currentWrapElm = 0;
15172 
15173 							// Remove any br elements when we wrap things
15174 							if (format.block)
15175 								dom.remove(node);
15176 
15177 							return;
15178 						}
15179 
15180 						// If node is wrapper type
15181 						if (format.wrapper && matchNode(node, name, vars)) {
15182 							currentWrapElm = 0;
15183 							return;
15184 						}
15185 
15186 						// Can we rename the block
15187 						if (contentEditable && !hasContentEditableState && format.block && !format.wrapper && isTextBlock(nodeName)) {
15188 							node = dom.rename(node, wrapName);
15189 							setElementFormat(node);
15190 							newWrappers.push(node);
15191 							currentWrapElm = 0;
15192 							return;
15193 						}
15194 
15195 						// Handle selector patterns
15196 						if (format.selector) {
15197 							// Look for matching formats
15198 							each(formatList, function(format) {
15199 								// Check collapsed state if it exists
15200 								if ('collapsed' in format && format.collapsed !== isCollapsed) {
15201 									return;
15202 								}
15203 
15204 								if (dom.is(node, format.selector) && !isCaretNode(node)) {
15205 									setElementFormat(node, format);
15206 									found = true;
15207 								}
15208 							});
15209 
15210 							// Continue processing if a selector match wasn't found and a inline element is defined
15211 							if (!format.inline || found) {
15212 								currentWrapElm = 0;
15213 								return;
15214 							}
15215 						}
15216 
15217 						// Is it valid to wrap this item
15218 						if (contentEditable && !hasContentEditableState && isValid(wrapName, nodeName) && isValid(parentName, wrapName) &&
15219 								!(!node_specific && node.nodeType === 3 && node.nodeValue.length === 1 && node.nodeValue.charCodeAt(0) === 65279) && !isCaretNode(node)) {
15220 							// Start wrapping
15221 							if (!currentWrapElm) {
15222 								// Wrap the node
15223 								currentWrapElm = dom.clone(wrapElm, FALSE);
15224 								node.parentNode.insertBefore(currentWrapElm, node);
15225 								newWrappers.push(currentWrapElm);
15226 							}
15227 
15228 							currentWrapElm.appendChild(node);
15229 						} else if (nodeName == 'li' && bookmark) {
15230 							// Start wrapping - if we are in a list node and have a bookmark, then we will always begin by wrapping in a new element.
15231 							currentWrapElm = applyStyleToList(node, bookmark, wrapElm, newWrappers, process);
15232 						} else {
15233 							// Start a new wrapper for possible children
15234 							currentWrapElm = 0;
15235 							
15236 							each(tinymce.grep(node.childNodes), process);
15237 
15238 							if (hasContentEditableState) {
15239 								contentEditable = lastContentEditable; // Restore last contentEditable state from stack
15240 							}
15241 
15242 							// End the last wrapper
15243 							currentWrapElm = 0;
15244 						}
15245 					};
15246 
15247 					// Process siblings from range
15248 					each(nodes, process);
15249 				});
15250 
15251 				// Wrap links inside as well, for example color inside a link when the wrapper is around the link
15252 				if (format.wrap_links === false) {
15253 					each(newWrappers, function(node) {
15254 						function process(node) {
15255 							var i, currentWrapElm, children;
15256 
15257 							if (node.nodeName === 'A') {
15258 								currentWrapElm = dom.clone(wrapElm, FALSE);
15259 								newWrappers.push(currentWrapElm);
15260 
15261 								children = tinymce.grep(node.childNodes);
15262 								for (i = 0; i < children.length; i++)
15263 									currentWrapElm.appendChild(children[i]);
15264 
15265 								node.appendChild(currentWrapElm);
15266 							}
15267 
15268 							each(tinymce.grep(node.childNodes), process);
15269 						};
15270 
15271 						process(node);
15272 					});
15273 				}
15274 
15275 				// Cleanup
15276 				
15277 				each(newWrappers, function(node) {
15278 					var childCount;
15279 
15280 					function getChildCount(node) {
15281 						var count = 0;
15282 
15283 						each(node.childNodes, function(node) {
15284 							if (!isWhiteSpaceNode(node) && !isBookmarkNode(node))
15285 								count++;
15286 						});
15287 
15288 						return count;
15289 					};
15290 
15291 					function mergeStyles(node) {
15292 						var child, clone;
15293 
15294 						each(node.childNodes, function(node) {
15295 							if (node.nodeType == 1 && !isBookmarkNode(node) && !isCaretNode(node)) {
15296 								child = node;
15297 								return FALSE; // break loop
15298 							}
15299 						});
15300 
15301 						// If child was found and of the same type as the current node
15302 						if (child && matchName(child, format)) {
15303 							clone = dom.clone(child, FALSE);
15304 							setElementFormat(clone);
15305 
15306 							dom.replace(clone, node, TRUE);
15307 							dom.remove(child, 1);
15308 						}
15309 
15310 						return clone || node;
15311 					};
15312 
15313 					childCount = getChildCount(node);
15314 
15315 					// Remove empty nodes but only if there is multiple wrappers and they are not block
15316 					// elements so never remove single <h1></h1> since that would remove the currrent empty block element where the caret is at
15317 					if ((newWrappers.length > 1 || !isBlock(node)) && childCount === 0) {
15318 						dom.remove(node, 1);
15319 						return;
15320 					}
15321 
15322 					if (format.inline || format.wrapper) {
15323 						// Merges the current node with it's children of similar type to reduce the number of elements
15324 						if (!format.exact && childCount === 1)
15325 							node = mergeStyles(node);
15326 
15327 						// Remove/merge children
15328 						each(formatList, function(format) {
15329 							// Merge all children of similar type will move styles from child to parent
15330 							// this: <span style="color:red"><b><span style="color:red; font-size:10px">text</span></b></span>
15331 							// will become: <span style="color:red"><b><span style="font-size:10px">text</span></b></span>
15332 							each(dom.select(format.inline, node), function(child) {
15333 								var parent;
15334 
15335 								// When wrap_links is set to false we don't want
15336 								// to remove the format on children within links
15337 								if (format.wrap_links === false) {
15338 									parent = child.parentNode;
15339 
15340 									do {
15341 										if (parent.nodeName === 'A')
15342 											return;
15343 									} while (parent = parent.parentNode);
15344 								}
15345 
15346 								removeFormat(format, vars, child, format.exact ? child : null);
15347 							});
15348 						});
15349 
15350 						// Remove child if direct parent is of same type
15351 						if (matchNode(node.parentNode, name, vars)) {
15352 							dom.remove(node, 1);
15353 							node = 0;
15354 							return TRUE;
15355 						}
15356 
15357 						// Look for parent with similar style format
15358 						if (format.merge_with_parents) {
15359 							dom.getParent(node.parentNode, function(parent) {
15360 								if (matchNode(parent, name, vars)) {
15361 									dom.remove(node, 1);
15362 									node = 0;
15363 									return TRUE;
15364 								}
15365 							});
15366 						}
15367 
15368 						// Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b>
15369 						if (node && format.merge_siblings !== false) {
15370 							node = mergeSiblings(getNonWhiteSpaceSibling(node), node);
15371 							node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE));
15372 						}
15373 					}
15374 				});
15375 			};
15376 
15377 			if (format) {
15378 				if (node) {
15379 					if (node.nodeType) {
15380 						rng = dom.createRng();
15381 						rng.setStartBefore(node);
15382 						rng.setEndAfter(node);
15383 						applyRngStyle(expandRng(rng, formatList), null, true);
15384 					} else {
15385 						applyRngStyle(node, null, true);
15386 					}
15387 				} else {
15388 					if (!isCollapsed || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) {
15389 						// Obtain selection node before selection is unselected by applyRngStyle()
15390 						var curSelNode = ed.selection.getNode();
15391 
15392 						// If the formats have a default block and we can't find a parent block then start wrapping it with a DIV this is for forced_root_blocks: false
15393 						// It's kind of a hack but people should be using the default block type P since all desktop editors work that way
15394 						if (!forcedRootBlock && formatList[0].defaultBlock && !dom.getParent(curSelNode, dom.isBlock)) {
15395 							apply(formatList[0].defaultBlock);
15396 						}
15397 
15398 						// Apply formatting to selection
15399 						ed.selection.setRng(adjustSelectionToVisibleSelection());
15400 						bookmark = selection.getBookmark();
15401 						applyRngStyle(expandRng(selection.getRng(TRUE), formatList), bookmark);
15402 
15403 						// Colored nodes should be underlined so that the color of the underline matches the text color.
15404 						if (format.styles && (format.styles.color || format.styles.textDecoration)) {
15405 							tinymce.walk(curSelNode, processUnderlineAndColor, 'childNodes');
15406 							processUnderlineAndColor(curSelNode);
15407 						}
15408 
15409 						selection.moveToBookmark(bookmark);
15410 						moveStart(selection.getRng(TRUE));
15411 						ed.nodeChanged();
15412 					} else
15413 						performCaretAction('apply', name, vars);
15414 				}
15415 			}
15416 		};
15417 
15418 		function remove(name, vars, node) {
15419 			var formatList = get(name), format = formatList[0], bookmark, i, rng, contentEditable = true;
15420 
15421 			// Merges the styles for each node
15422 			function process(node) {
15423 				var children, i, l, localContentEditable, lastContentEditable, hasContentEditableState;
15424 
15425 				// Node has a contentEditable value
15426 				if (node.nodeType === 1 && getContentEditable(node)) {
15427 					lastContentEditable = contentEditable;
15428 					contentEditable = getContentEditable(node) === "true";
15429 					hasContentEditableState = true; // We don't want to wrap the container only it's children
15430 				}
15431 
15432 				// Grab the children first since the nodelist might be changed
15433 				children = tinymce.grep(node.childNodes);
15434 
15435 				// Process current node
15436 				if (contentEditable && !hasContentEditableState) {
15437 					for (i = 0, l = formatList.length; i < l; i++) {
15438 						if (removeFormat(formatList[i], vars, node, node))
15439 							break;
15440 					}
15441 				}
15442 
15443 				// Process the children
15444 				if (format.deep) {
15445 					if (children.length) {					
15446 						for (i = 0, l = children.length; i < l; i++)
15447 							process(children[i]);
15448 
15449 						if (hasContentEditableState) {
15450 							contentEditable = lastContentEditable; // Restore last contentEditable state from stack
15451 						}
15452 					}
15453 				}
15454 			};
15455 
15456 			function findFormatRoot(container) {
15457 				var formatRoot;
15458 
15459 				// Find format root
15460 				each(getParents(container.parentNode).reverse(), function(parent) {
15461 					var format;
15462 
15463 					// Find format root element
15464 					if (!formatRoot && parent.id != '_start' && parent.id != '_end') {
15465 						// Is the node matching the format we are looking for
15466 						format = matchNode(parent, name, vars);
15467 						if (format && format.split !== false)
15468 							formatRoot = parent;
15469 					}
15470 				});
15471 
15472 				return formatRoot;
15473 			};
15474 
15475 			function wrapAndSplit(format_root, container, target, split) {
15476 				var parent, clone, lastClone, firstClone, i, formatRootParent;
15477 
15478 				// Format root found then clone formats and split it
15479 				if (format_root) {
15480 					formatRootParent = format_root.parentNode;
15481 
15482 					for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) {
15483 						clone = dom.clone(parent, FALSE);
15484 
15485 						for (i = 0; i < formatList.length; i++) {
15486 							if (removeFormat(formatList[i], vars, clone, clone)) {
15487 								clone = 0;
15488 								break;
15489 							}
15490 						}
15491 
15492 						// Build wrapper node
15493 						if (clone) {
15494 							if (lastClone)
15495 								clone.appendChild(lastClone);
15496 
15497 							if (!firstClone)
15498 								firstClone = clone;
15499 
15500 							lastClone = clone;
15501 						}
15502 					}
15503 
15504 					// Never split block elements if the format is mixed
15505 					if (split && (!format.mixed || !isBlock(format_root)))
15506 						container = dom.split(format_root, container);
15507 
15508 					// Wrap container in cloned formats
15509 					if (lastClone) {
15510 						target.parentNode.insertBefore(lastClone, target);
15511 						firstClone.appendChild(target);
15512 					}
15513 				}
15514 
15515 				return container;
15516 			};
15517 
15518 			function splitToFormatRoot(container) {
15519 				return wrapAndSplit(findFormatRoot(container), container, container, true);
15520 			};
15521 
15522 			function unwrap(start) {
15523 				var node = dom.get(start ? '_start' : '_end'),
15524 					out = node[start ? 'firstChild' : 'lastChild'];
15525 
15526 				// If the end is placed within the start the result will be removed
15527 				// So this checks if the out node is a bookmark node if it is it
15528 				// checks for another more suitable node
15529 				if (isBookmarkNode(out))
15530 					out = out[start ? 'firstChild' : 'lastChild'];
15531 
15532 				dom.remove(node, true);
15533 
15534 				return out;
15535 			};
15536 
15537 			function removeRngStyle(rng) {
15538 				var startContainer, endContainer, node;
15539 
15540 				rng = expandRng(rng, formatList, TRUE);
15541 
15542 				if (format.split) {
15543 					startContainer = getContainer(rng, TRUE);
15544 					endContainer = getContainer(rng);
15545 
15546 					if (startContainer != endContainer) {
15547 						// WebKit will render the table incorrectly if we wrap a TD in a SPAN so lets see if the can use the first child instead
15548 						// This will happen if you tripple click a table cell and use remove formatting
15549 						if (/^(TR|TD)$/.test(startContainer.nodeName) && startContainer.firstChild) {
15550 							startContainer = (startContainer.nodeName == "TD" ? startContainer.firstChild : startContainer.firstChild.firstChild) || startContainer;
15551 						}
15552 
15553 						// Wrap start/end nodes in span element since these might be cloned/moved
15554 						startContainer = wrap(startContainer, 'span', {id : '_start', 'data-mce-type' : 'bookmark'});
15555 						endContainer = wrap(endContainer, 'span', {id : '_end', 'data-mce-type' : 'bookmark'});
15556 
15557 						// Split start/end
15558 						splitToFormatRoot(startContainer);
15559 						splitToFormatRoot(endContainer);
15560 
15561 						// Unwrap start/end to get real elements again
15562 						startContainer = unwrap(TRUE);
15563 						endContainer = unwrap();
15564 					} else
15565 						startContainer = endContainer = splitToFormatRoot(startContainer);
15566 
15567 					// Update range positions since they might have changed after the split operations
15568 					rng.startContainer = startContainer.parentNode;
15569 					rng.startOffset = nodeIndex(startContainer);
15570 					rng.endContainer = endContainer.parentNode;
15571 					rng.endOffset = nodeIndex(endContainer) + 1;
15572 				}
15573 
15574 				// Remove items between start/end
15575 				rangeUtils.walk(rng, function(nodes) {
15576 					each(nodes, function(node) {
15577 						process(node);
15578 
15579 						// Remove parent span if it only contains text-decoration: underline, yet a parent node is also underlined.
15580 						if (node.nodeType === 1 && ed.dom.getStyle(node, 'text-decoration') === 'underline' && node.parentNode && getTextDecoration(node.parentNode) === 'underline') {
15581 							removeFormat({'deep': false, 'exact': true, 'inline': 'span', 'styles': {'textDecoration' : 'underline'}}, null, node);
15582 						}
15583 					});
15584 				});
15585 			};
15586 
15587 			// Handle node
15588 			if (node) {
15589 				if (node.nodeType) {
15590 					rng = dom.createRng();
15591 					rng.setStartBefore(node);
15592 					rng.setEndAfter(node);
15593 					removeRngStyle(rng);
15594 				} else {
15595 					removeRngStyle(node);
15596 				}
15597 
15598 				return;
15599 			}
15600 
15601 			if (!selection.isCollapsed() || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) {
15602 				bookmark = selection.getBookmark();
15603 				removeRngStyle(selection.getRng(TRUE));
15604 				selection.moveToBookmark(bookmark);
15605 
15606 				// Check if start element still has formatting then we are at: "<b>text|</b>text" and need to move the start into the next text node
15607 				if (format.inline && match(name, vars, selection.getStart())) {
15608 					moveStart(selection.getRng(true));
15609 				}
15610 
15611 				ed.nodeChanged();
15612 			} else
15613 				performCaretAction('remove', name, vars);
15614 		};
15615 
15616 		function toggle(name, vars, node) {
15617 			var fmt = get(name);
15618 
15619 			if (match(name, vars, node) && (!('toggle' in fmt[0]) || fmt[0].toggle))
15620 				remove(name, vars, node);
15621 			else
15622 				apply(name, vars, node);
15623 		};
15624 
15625 		function matchNode(node, name, vars, similar) {
15626 			var formatList = get(name), format, i, classes;
15627 
15628 			function matchItems(node, format, item_name) {
15629 				var key, value, items = format[item_name], i;
15630 
15631 				// Custom match
15632 				if (format.onmatch) {
15633 					return format.onmatch(node, format, item_name);
15634 				}
15635 
15636 				// Check all items
15637 				if (items) {
15638 					// Non indexed object
15639 					if (items.length === undef) {
15640 						for (key in items) {
15641 							if (items.hasOwnProperty(key)) {
15642 								if (item_name === 'attributes')
15643 									value = dom.getAttrib(node, key);
15644 								else
15645 									value = getStyle(node, key);
15646 
15647 								if (similar && !value && !format.exact)
15648 									return;
15649 
15650 								if ((!similar || format.exact) && !isEq(value, replaceVars(items[key], vars)))
15651 									return;
15652 							}
15653 						}
15654 					} else {
15655 						// Only one match needed for indexed arrays
15656 						for (i = 0; i < items.length; i++) {
15657 							if (item_name === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i]))
15658 								return format;
15659 						}
15660 					}
15661 				}
15662 
15663 				return format;
15664 			};
15665 
15666 			if (formatList && node) {
15667 				// Check each format in list
15668 				for (i = 0; i < formatList.length; i++) {
15669 					format = formatList[i];
15670 
15671 					// Name name, attributes, styles and classes
15672 					if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) {
15673 						// Match classes
15674 						if (classes = format.classes) {
15675 							for (i = 0; i < classes.length; i++) {
15676 								if (!dom.hasClass(node, classes[i]))
15677 									return;
15678 							}
15679 						}
15680 
15681 						return format;
15682 					}
15683 				}
15684 			}
15685 		};
15686 
15687 		function match(name, vars, node) {
15688 			var startNode;
15689 
15690 			function matchParents(node) {
15691 				// Find first node with similar format settings
15692 				node = dom.getParent(node, function(node) {
15693 					return !!matchNode(node, name, vars, true);
15694 				});
15695 
15696 				// Do an exact check on the similar format element
15697 				return matchNode(node, name, vars);
15698 			};
15699 
15700 			// Check specified node
15701 			if (node)
15702 				return matchParents(node);
15703 
15704 			// Check selected node
15705 			node = selection.getNode();
15706 			if (matchParents(node))
15707 				return TRUE;
15708 
15709 			// Check start node if it's different
15710 			startNode = selection.getStart();
15711 			if (startNode != node) {
15712 				if (matchParents(startNode))
15713 					return TRUE;
15714 			}
15715 
15716 			return FALSE;
15717 		};
15718 
15719 		function matchAll(names, vars) {
15720 			var startElement, matchedFormatNames = [], checkedMap = {}, i, ni, name;
15721 
15722 			// Check start of selection for formats
15723 			startElement = selection.getStart();
15724 			dom.getParent(startElement, function(node) {
15725 				var i, name;
15726 
15727 				for (i = 0; i < names.length; i++) {
15728 					name = names[i];
15729 
15730 					if (!checkedMap[name] && matchNode(node, name, vars)) {
15731 						checkedMap[name] = true;
15732 						matchedFormatNames.push(name);
15733 					}
15734 				}
15735 			}, dom.getRoot());
15736 
15737 			return matchedFormatNames;
15738 		};
15739 
15740 		function canApply(name) {
15741 			var formatList = get(name), startNode, parents, i, x, selector;
15742 
15743 			if (formatList) {
15744 				startNode = selection.getStart();
15745 				parents = getParents(startNode);
15746 
15747 				for (x = formatList.length - 1; x >= 0; x--) {
15748 					selector = formatList[x].selector;
15749 
15750 					// Format is not selector based, then always return TRUE
15751 					if (!selector)
15752 						return TRUE;
15753 
15754 					for (i = parents.length - 1; i >= 0; i--) {
15755 						if (dom.is(parents[i], selector))
15756 							return TRUE;
15757 					}
15758 				}
15759 			}
15760 
15761 			return FALSE;
15762 		};
15763 
15764 		function formatChanged(formats, callback) {
15765 			var currentFormats;
15766 
15767 			// Setup format node change logic
15768 			if (!formatChangeData) {
15769 				formatChangeData = {};
15770 				currentFormats = {};
15771 
15772 				ed.onNodeChange.addToTop(function(ed, cm, node) {
15773 					var parents = getParents(node), matchedFormats = {};
15774 
15775 					// Check for new formats
15776 					each(formatChangeData, function(callbacks, format) {
15777 						each(parents, function(node) {
15778 							if (matchNode(node, format, {}, true)) {
15779 								if (!currentFormats[format]) {
15780 									// Execute callbacks
15781 									each(callbacks, function(callback) {
15782 										callback(true, {node: node, format: format, parents: parents});
15783 									});
15784 
15785 									currentFormats[format] = callbacks;
15786 								}
15787 
15788 								matchedFormats[format] = callbacks;
15789 								return false;
15790 							}
15791 						});
15792 					});
15793 
15794 					// Check if current formats still match
15795 					each(currentFormats, function(callbacks, format) {
15796 						if (!matchedFormats[format]) {
15797 							delete currentFormats[format];
15798 
15799 							each(callbacks, function(callback) {
15800 								callback(false, {node: node, format: format, parents: parents});
15801 							});
15802 						}
15803 					});
15804 				});
15805 			}
15806 
15807 			// Add format listeners
15808 			each(formats.split(','), function(format) {
15809 				if (!formatChangeData[format]) {
15810 					formatChangeData[format] = [];
15811 				}
15812 
15813 				formatChangeData[format].push(callback);
15814 			});
15815 
15816 			return this;
15817 		};
15818 
15819 		// Expose to public
15820 		tinymce.extend(this, {
15821 			get : get,
15822 			register : register,
15823 			apply : apply,
15824 			remove : remove,
15825 			toggle : toggle,
15826 			match : match,
15827 			matchAll : matchAll,
15828 			matchNode : matchNode,
15829 			canApply : canApply,
15830 			formatChanged: formatChanged
15831 		});
15832 
15833 		// Initialize
15834 		defaultFormats();
15835 		addKeyboardShortcuts();
15836 
15837 		// Private functions
15838 
15839 		function matchName(node, format) {
15840 			// Check for inline match
15841 			if (isEq(node, format.inline))
15842 				return TRUE;
15843 
15844 			// Check for block match
15845 			if (isEq(node, format.block))
15846 				return TRUE;
15847 
15848 			// Check for selector match
15849 			if (format.selector)
15850 				return dom.is(node, format.selector);
15851 		};
15852 
15853 		function isEq(str1, str2) {
15854 			str1 = str1 || '';
15855 			str2 = str2 || '';
15856 
15857 			str1 = '' + (str1.nodeName || str1);
15858 			str2 = '' + (str2.nodeName || str2);
15859 
15860 			return str1.toLowerCase() == str2.toLowerCase();
15861 		};
15862 
15863 		function getStyle(node, name) {
15864 			var styleVal = dom.getStyle(node, name);
15865 
15866 			// Force the format to hex
15867 			if (name == 'color' || name == 'backgroundColor')
15868 				styleVal = dom.toHex(styleVal);
15869 
15870 			// Opera will return bold as 700
15871 			if (name == 'fontWeight' && styleVal == 700)
15872 				styleVal = 'bold';
15873 
15874 			return '' + styleVal;
15875 		};
15876 
15877 		function replaceVars(value, vars) {
15878 			if (typeof(value) != "string")
15879 				value = value(vars);
15880 			else if (vars) {
15881 				value = value.replace(/%(\w+)/g, function(str, name) {
15882 					return vars[name] || str;
15883 				});
15884 			}
15885 
15886 			return value;
15887 		};
15888 
15889 		function isWhiteSpaceNode(node) {
15890 			return node && node.nodeType === 3 && /^([\t \r\n]+|)$/.test(node.nodeValue);
15891 		};
15892 
15893 		function wrap(node, name, attrs) {
15894 			var wrapper = dom.create(name, attrs);
15895 
15896 			node.parentNode.insertBefore(wrapper, node);
15897 			wrapper.appendChild(node);
15898 
15899 			return wrapper;
15900 		};
15901 
15902 		function expandRng(rng, format, remove) {
15903 			var sibling, lastIdx, leaf, endPoint,
15904 				startContainer = rng.startContainer,
15905 				startOffset = rng.startOffset,
15906 				endContainer = rng.endContainer,
15907 				endOffset = rng.endOffset;
15908 
15909 			// This function walks up the tree if there is no siblings before/after the node
15910 			function findParentContainer(start) {
15911 				var container, parent, child, sibling, siblingName, root;
15912 
15913 				container = parent = start ? startContainer : endContainer;
15914 				siblingName = start ? 'previousSibling' : 'nextSibling';
15915 				root = dom.getRoot();
15916 
15917 				// If it's a text node and the offset is inside the text
15918 				if (container.nodeType == 3 && !isWhiteSpaceNode(container)) {
15919 					if (start ? startOffset > 0 : endOffset < container.nodeValue.length) {
15920 						return container;
15921 					}
15922 				}
15923 
15924 				for (;;) {
15925 					// Stop expanding on block elements
15926 					if (!format[0].block_expand && isBlock(parent))
15927 						return parent;
15928 
15929 					// Walk left/right
15930 					for (sibling = parent[siblingName]; sibling; sibling = sibling[siblingName]) {
15931 						if (!isBookmarkNode(sibling) && !isWhiteSpaceNode(sibling)) {
15932 							return parent;
15933 						}
15934 					}
15935 
15936 					// Check if we can move up are we at root level or body level
15937 					if (parent.parentNode == root) {
15938 						container = parent;
15939 						break;
15940 					}
15941 
15942 					parent = parent.parentNode;
15943 				}
15944 
15945 				return container;
15946 			};
15947 
15948 			// This function walks down the tree to find the leaf at the selection.
15949 			// The offset is also returned as if node initially a leaf, the offset may be in the middle of the text node.
15950 			function findLeaf(node, offset) {
15951 				if (offset === undef)
15952 					offset = node.nodeType === 3 ? node.length : node.childNodes.length;
15953 				while (node && node.hasChildNodes()) {
15954 					node = node.childNodes[offset];
15955 					if (node)
15956 						offset = node.nodeType === 3 ? node.length : node.childNodes.length;
15957 				}
15958 				return { node: node, offset: offset };
15959 			}
15960 
15961 			// If index based start position then resolve it
15962 			if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) {
15963 				lastIdx = startContainer.childNodes.length - 1;
15964 				startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset];
15965 
15966 				if (startContainer.nodeType == 3)
15967 					startOffset = 0;
15968 			}
15969 
15970 			// If index based end position then resolve it
15971 			if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) {
15972 				lastIdx = endContainer.childNodes.length - 1;
15973 				endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1];
15974 
15975 				if (endContainer.nodeType == 3)
15976 					endOffset = endContainer.nodeValue.length;
15977 			}
15978 
15979 			// Expands the node to the closes contentEditable false element if it exists
15980 			function findParentContentEditable(node) {
15981 				var parent = node;
15982 
15983 				while (parent) {
15984 					if (parent.nodeType === 1 && getContentEditable(parent)) {
15985 						return getContentEditable(parent) === "false" ? parent : node;
15986 					}
15987 
15988 					parent = parent.parentNode;
15989 				}
15990 
15991 				return node;
15992 			};
15993 
15994 			function findWordEndPoint(container, offset, start) {
15995 				var walker, node, pos, lastTextNode;
15996 
15997 				function findSpace(node, offset) {
15998 					var pos, pos2, str = node.nodeValue;
15999 
16000 					if (typeof(offset) == "undefined") {
16001 						offset = start ? str.length : 0;
16002 					}
16003 
16004 					if (start) {
16005 						pos = str.lastIndexOf(' ', offset);
16006 						pos2 = str.lastIndexOf('\u00a0', offset);
16007 						pos = pos > pos2 ? pos : pos2;
16008 
16009 						// Include the space on remove to avoid tag soup
16010 						if (pos !== -1 && !remove) {
16011 							pos++;
16012 						}
16013 					} else {
16014 						pos = str.indexOf(' ', offset);
16015 						pos2 = str.indexOf('\u00a0', offset);
16016 						pos = pos !== -1 && (pos2 === -1 || pos < pos2) ? pos : pos2;
16017 					}
16018 
16019 					return pos;
16020 				};
16021 
16022 				if (container.nodeType === 3) {
16023 					pos = findSpace(container, offset);
16024 
16025 					if (pos !== -1) {
16026 						return {container : container, offset : pos};
16027 					}
16028 
16029 					lastTextNode = container;
16030 				}
16031 
16032 				// Walk the nodes inside the block
16033 				walker = new TreeWalker(container, dom.getParent(container, isBlock) || ed.getBody());
16034 				while (node = walker[start ? 'prev' : 'next']()) {
16035 					if (node.nodeType === 3) {
16036 						lastTextNode = node;
16037 						pos = findSpace(node);
16038 
16039 						if (pos !== -1) {
16040 							return {container : node, offset : pos};
16041 						}
16042 					} else if (isBlock(node)) {
16043 						break;
16044 					}
16045 				}
16046 
16047 				if (lastTextNode) {
16048 					if (start) {
16049 						offset = 0;
16050 					} else {
16051 						offset = lastTextNode.length;
16052 					}
16053 
16054 					return {container: lastTextNode, offset: offset};
16055 				}
16056 			};
16057 
16058 			function findSelectorEndPoint(container, sibling_name) {
16059 				var parents, i, y, curFormat;
16060 
16061 				if (container.nodeType == 3 && container.nodeValue.length === 0 && container[sibling_name])
16062 					container = container[sibling_name];
16063 
16064 				parents = getParents(container);
16065 				for (i = 0; i < parents.length; i++) {
16066 					for (y = 0; y < format.length; y++) {
16067 						curFormat = format[y];
16068 
16069 						// If collapsed state is set then skip formats that doesn't match that
16070 						if ("collapsed" in curFormat && curFormat.collapsed !== rng.collapsed)
16071 							continue;
16072 
16073 						if (dom.is(parents[i], curFormat.selector))
16074 							return parents[i];
16075 					}
16076 				}
16077 
16078 				return container;
16079 			};
16080 
16081 			function findBlockEndPoint(container, sibling_name, sibling_name2) {
16082 				var node;
16083 
16084 				// Expand to block of similar type
16085 				if (!format[0].wrapper)
16086 					node = dom.getParent(container, format[0].block);
16087 
16088 				// Expand to first wrappable block element or any block element
16089 				if (!node)
16090 					node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, isBlock);
16091 
16092 				// Exclude inner lists from wrapping
16093 				if (node && format[0].wrapper)
16094 					node = getParents(node, 'ul,ol').reverse()[0] || node;
16095 
16096 				// Didn't find a block element look for first/last wrappable element
16097 				if (!node) {
16098 					node = container;
16099 
16100 					while (node[sibling_name] && !isBlock(node[sibling_name])) {
16101 						node = node[sibling_name];
16102 
16103 						// Break on BR but include it will be removed later on
16104 						// we can't remove it now since we need to check if it can be wrapped
16105 						if (isEq(node, 'br'))
16106 							break;
16107 					}
16108 				}
16109 
16110 				return node || container;
16111 			};
16112 
16113 			// Expand to closest contentEditable element
16114 			startContainer = findParentContentEditable(startContainer);
16115 			endContainer = findParentContentEditable(endContainer);
16116 
16117 			// Exclude bookmark nodes if possible
16118 			if (isBookmarkNode(startContainer.parentNode) || isBookmarkNode(startContainer)) {
16119 				startContainer = isBookmarkNode(startContainer) ? startContainer : startContainer.parentNode;
16120 				startContainer = startContainer.nextSibling || startContainer;
16121 
16122 				if (startContainer.nodeType == 3)
16123 					startOffset = 0;
16124 			}
16125 
16126 			if (isBookmarkNode(endContainer.parentNode) || isBookmarkNode(endContainer)) {
16127 				endContainer = isBookmarkNode(endContainer) ? endContainer : endContainer.parentNode;
16128 				endContainer = endContainer.previousSibling || endContainer;
16129 
16130 				if (endContainer.nodeType == 3)
16131 					endOffset = endContainer.length;
16132 			}
16133 
16134 			if (format[0].inline) {
16135 				if (rng.collapsed) {
16136 					// Expand left to closest word boundery
16137 					endPoint = findWordEndPoint(startContainer, startOffset, true);
16138 					if (endPoint) {
16139 						startContainer = endPoint.container;
16140 						startOffset = endPoint.offset;
16141 					}
16142 
16143 					// Expand right to closest word boundery
16144 					endPoint = findWordEndPoint(endContainer, endOffset);
16145 					if (endPoint) {
16146 						endContainer = endPoint.container;
16147 						endOffset = endPoint.offset;
16148 					}
16149 				}
16150 
16151 				// Avoid applying formatting to a trailing space.
16152 				leaf = findLeaf(endContainer, endOffset);
16153 				if (leaf.node) {
16154 					while (leaf.node && leaf.offset === 0 && leaf.node.previousSibling)
16155 						leaf = findLeaf(leaf.node.previousSibling);
16156 
16157 					if (leaf.node && leaf.offset > 0 && leaf.node.nodeType === 3 &&
16158 							leaf.node.nodeValue.charAt(leaf.offset - 1) === ' ') {
16159 
16160 						if (leaf.offset > 1) {
16161 							endContainer = leaf.node;
16162 							endContainer.splitText(leaf.offset - 1);
16163 						}
16164 					}
16165 				}
16166 			}
16167 
16168 			// Move start/end point up the tree if the leaves are sharp and if we are in different containers
16169 			// Example * becomes !: !<p><b><i>*text</i><i>text*</i></b></p>!
16170 			// This will reduce the number of wrapper elements that needs to be created
16171 			// Move start point up the tree
16172 			if (format[0].inline || format[0].block_expand) {
16173 				if (!format[0].inline || (startContainer.nodeType != 3 || startOffset === 0)) {
16174 					startContainer = findParentContainer(true);
16175 				}
16176 
16177 				if (!format[0].inline || (endContainer.nodeType != 3 || endOffset === endContainer.nodeValue.length)) {
16178 					endContainer = findParentContainer();
16179 				}
16180 			}
16181 
16182 			// Expand start/end container to matching selector
16183 			if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) {
16184 				// Find new startContainer/endContainer if there is better one
16185 				startContainer = findSelectorEndPoint(startContainer, 'previousSibling');
16186 				endContainer = findSelectorEndPoint(endContainer, 'nextSibling');
16187 			}
16188 
16189 			// Expand start/end container to matching block element or text node
16190 			if (format[0].block || format[0].selector) {
16191 				// Find new startContainer/endContainer if there is better one
16192 				startContainer = findBlockEndPoint(startContainer, 'previousSibling');
16193 				endContainer = findBlockEndPoint(endContainer, 'nextSibling');
16194 
16195 				// Non block element then try to expand up the leaf
16196 				if (format[0].block) {
16197 					if (!isBlock(startContainer))
16198 						startContainer = findParentContainer(true);
16199 
16200 					if (!isBlock(endContainer))
16201 						endContainer = findParentContainer();
16202 				}
16203 			}
16204 
16205 			// Setup index for startContainer
16206 			if (startContainer.nodeType == 1) {
16207 				startOffset = nodeIndex(startContainer);
16208 				startContainer = startContainer.parentNode;
16209 			}
16210 
16211 			// Setup index for endContainer
16212 			if (endContainer.nodeType == 1) {
16213 				endOffset = nodeIndex(endContainer) + 1;
16214 				endContainer = endContainer.parentNode;
16215 			}
16216 
16217 			// Return new range like object
16218 			return {
16219 				startContainer : startContainer,
16220 				startOffset : startOffset,
16221 				endContainer : endContainer,
16222 				endOffset : endOffset
16223 			};
16224 		}
16225 
16226 		function removeFormat(format, vars, node, compare_node) {
16227 			var i, attrs, stylesModified;
16228 
16229 			// Check if node matches format
16230 			if (!matchName(node, format))
16231 				return FALSE;
16232 
16233 			// Should we compare with format attribs and styles
16234 			if (format.remove != 'all') {
16235 				// Remove styles
16236 				each(format.styles, function(value, name) {
16237 					value = replaceVars(value, vars);
16238 
16239 					// Indexed array
16240 					if (typeof(name) === 'number') {
16241 						name = value;
16242 						compare_node = 0;
16243 					}
16244 
16245 					if (!compare_node || isEq(getStyle(compare_node, name), value))
16246 						dom.setStyle(node, name, '');
16247 
16248 					stylesModified = 1;
16249 				});
16250 
16251 				// Remove style attribute if it's empty
16252 				if (stylesModified && dom.getAttrib(node, 'style') == '') {
16253 					node.removeAttribute('style');
16254 					node.removeAttribute('data-mce-style');
16255 				}
16256 
16257 				// Remove attributes
16258 				each(format.attributes, function(value, name) {
16259 					var valueOut;
16260 
16261 					value = replaceVars(value, vars);
16262 
16263 					// Indexed array
16264 					if (typeof(name) === 'number') {
16265 						name = value;
16266 						compare_node = 0;
16267 					}
16268 
16269 					if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) {
16270 						// Keep internal classes
16271 						if (name == 'class') {
16272 							value = dom.getAttrib(node, name);
16273 							if (value) {
16274 								// Build new class value where everything is removed except the internal prefixed classes
16275 								valueOut = '';
16276 								each(value.split(/\s+/), function(cls) {
16277 									if (/mce\w+/.test(cls))
16278 										valueOut += (valueOut ? ' ' : '') + cls;
16279 								});
16280 
16281 								// We got some internal classes left
16282 								if (valueOut) {
16283 									dom.setAttrib(node, name, valueOut);
16284 									return;
16285 								}
16286 							}
16287 						}
16288 
16289 						// IE6 has a bug where the attribute doesn't get removed correctly
16290 						if (name == "class")
16291 							node.removeAttribute('className');
16292 
16293 						// Remove mce prefixed attributes
16294 						if (MCE_ATTR_RE.test(name))
16295 							node.removeAttribute('data-mce-' + name);
16296 
16297 						node.removeAttribute(name);
16298 					}
16299 				});
16300 
16301 				// Remove classes
16302 				each(format.classes, function(value) {
16303 					value = replaceVars(value, vars);
16304 
16305 					if (!compare_node || dom.hasClass(compare_node, value))
16306 						dom.removeClass(node, value);
16307 				});
16308 
16309 				// Check for non internal attributes
16310 				attrs = dom.getAttribs(node);
16311 				for (i = 0; i < attrs.length; i++) {
16312 					if (attrs[i].nodeName.indexOf('_') !== 0)
16313 						return FALSE;
16314 				}
16315 			}
16316 
16317 			// Remove the inline child if it's empty for example <b> or <span>
16318 			if (format.remove != 'none') {
16319 				removeNode(node, format);
16320 				return TRUE;
16321 			}
16322 		};
16323 
16324 		function removeNode(node, format) {
16325 			var parentNode = node.parentNode, rootBlockElm;
16326 
16327 			function find(node, next, inc) {
16328 				node = getNonWhiteSpaceSibling(node, next, inc);
16329 
16330 				return !node || (node.nodeName == 'BR' || isBlock(node));
16331 			};
16332 
16333 			if (format.block) {
16334 				if (!forcedRootBlock) {
16335 					// Append BR elements if needed before we remove the block
16336 					if (isBlock(node) && !isBlock(parentNode)) {
16337 						if (!find(node, FALSE) && !find(node.firstChild, TRUE, 1))
16338 							node.insertBefore(dom.create('br'), node.firstChild);
16339 
16340 						if (!find(node, TRUE) && !find(node.lastChild, FALSE, 1))
16341 							node.appendChild(dom.create('br'));
16342 					}
16343 				} else {
16344 					// Wrap the block in a forcedRootBlock if we are at the root of document
16345 					if (parentNode == dom.getRoot()) {
16346 						if (!format.list_block || !isEq(node, format.list_block)) {
16347 							each(tinymce.grep(node.childNodes), function(node) {
16348 								if (isValid(forcedRootBlock, node.nodeName.toLowerCase())) {
16349 									if (!rootBlockElm)
16350 										rootBlockElm = wrap(node, forcedRootBlock);
16351 									else
16352 										rootBlockElm.appendChild(node);
16353 								} else
16354 									rootBlockElm = 0;
16355 							});
16356 						}
16357 					}
16358 				}
16359 			}
16360 
16361 			// Never remove nodes that isn't the specified inline element if a selector is specified too
16362 			if (format.selector && format.inline && !isEq(format.inline, node))
16363 				return;
16364 
16365 			dom.remove(node, 1);
16366 		};
16367 
16368 		function getNonWhiteSpaceSibling(node, next, inc) {
16369 			if (node) {
16370 				next = next ? 'nextSibling' : 'previousSibling';
16371 
16372 				for (node = inc ? node : node[next]; node; node = node[next]) {
16373 					if (node.nodeType == 1 || !isWhiteSpaceNode(node))
16374 						return node;
16375 				}
16376 			}
16377 		};
16378 
16379 		function isBookmarkNode(node) {
16380 			return node && node.nodeType == 1 && node.getAttribute('data-mce-type') == 'bookmark';
16381 		};
16382 
16383 		function mergeSiblings(prev, next) {
16384 			var marker, sibling, tmpSibling;
16385 
16386 			function compareElements(node1, node2) {
16387 				// Not the same name
16388 				if (node1.nodeName != node2.nodeName)
16389 					return FALSE;
16390 
16391 				function getAttribs(node) {
16392 					var attribs = {};
16393 
16394 					each(dom.getAttribs(node), function(attr) {
16395 						var name = attr.nodeName.toLowerCase();
16396 
16397 						// Don't compare internal attributes or style
16398 						if (name.indexOf('_') !== 0 && name !== 'style')
16399 							attribs[name] = dom.getAttrib(node, name);
16400 					});
16401 
16402 					return attribs;
16403 				};
16404 
16405 				function compareObjects(obj1, obj2) {
16406 					var value, name;
16407 
16408 					for (name in obj1) {
16409 						// Obj1 has item obj2 doesn't have
16410 						if (obj1.hasOwnProperty(name)) {
16411 							value = obj2[name];
16412 
16413 							// Obj2 doesn't have obj1 item
16414 							if (value === undef)
16415 								return FALSE;
16416 
16417 							// Obj2 item has a different value
16418 							if (obj1[name] != value)
16419 								return FALSE;
16420 
16421 							// Delete similar value
16422 							delete obj2[name];
16423 						}
16424 					}
16425 
16426 					// Check if obj 2 has something obj 1 doesn't have
16427 					for (name in obj2) {
16428 						// Obj2 has item obj1 doesn't have
16429 						if (obj2.hasOwnProperty(name))
16430 							return FALSE;
16431 					}
16432 
16433 					return TRUE;
16434 				};
16435 
16436 				// Attribs are not the same
16437 				if (!compareObjects(getAttribs(node1), getAttribs(node2)))
16438 					return FALSE;
16439 
16440 				// Styles are not the same
16441 				if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style'))))
16442 					return FALSE;
16443 
16444 				return TRUE;
16445 			};
16446 
16447 			function findElementSibling(node, sibling_name) {
16448 				for (sibling = node; sibling; sibling = sibling[sibling_name]) {
16449 					if (sibling.nodeType == 3 && sibling.nodeValue.length !== 0)
16450 						return node;
16451 
16452 					if (sibling.nodeType == 1 && !isBookmarkNode(sibling))
16453 						return sibling;
16454 				}
16455 
16456 				return node;
16457 			};
16458 
16459 			// Check if next/prev exists and that they are elements
16460 			if (prev && next) {
16461 				// If previous sibling is empty then jump over it
16462 				prev = findElementSibling(prev, 'previousSibling');
16463 				next = findElementSibling(next, 'nextSibling');
16464 
16465 				// Compare next and previous nodes
16466 				if (compareElements(prev, next)) {
16467 					// Append nodes between
16468 					for (sibling = prev.nextSibling; sibling && sibling != next;) {
16469 						tmpSibling = sibling;
16470 						sibling = sibling.nextSibling;
16471 						prev.appendChild(tmpSibling);
16472 					}
16473 
16474 					// Remove next node
16475 					dom.remove(next);
16476 
16477 					// Move children into prev node
16478 					each(tinymce.grep(next.childNodes), function(node) {
16479 						prev.appendChild(node);
16480 					});
16481 
16482 					return prev;
16483 				}
16484 			}
16485 
16486 			return next;
16487 		};
16488 
16489 		function isTextBlock(name) {
16490 			return /^(h[1-6]|p|div|pre|address|dl|dt|dd)$/.test(name);
16491 		};
16492 
16493 		function getContainer(rng, start) {
16494 			var container, offset, lastIdx, walker;
16495 
16496 			container = rng[start ? 'startContainer' : 'endContainer'];
16497 			offset = rng[start ? 'startOffset' : 'endOffset'];
16498 
16499 			if (container.nodeType == 1) {
16500 				lastIdx = container.childNodes.length - 1;
16501 
16502 				if (!start && offset)
16503 					offset--;
16504 
16505 				container = container.childNodes[offset > lastIdx ? lastIdx : offset];
16506 			}
16507 
16508 			// If start text node is excluded then walk to the next node
16509 			if (container.nodeType === 3 && start && offset >= container.nodeValue.length) {
16510 				container = new TreeWalker(container, ed.getBody()).next() || container;
16511 			}
16512 
16513 			// If end text node is excluded then walk to the previous node
16514 			if (container.nodeType === 3 && !start && offset === 0) {
16515 				container = new TreeWalker(container, ed.getBody()).prev() || container;
16516 			}
16517 
16518 			return container;
16519 		};
16520 
16521 		function performCaretAction(type, name, vars) {
16522 			var caretContainerId = '_mce_caret', debug = ed.settings.caret_debug;
16523 
16524 			// Creates a caret container bogus element
16525 			function createCaretContainer(fill) {
16526 				var caretContainer = dom.create('span', {id: caretContainerId, 'data-mce-bogus': true, style: debug ? 'color:red' : ''});
16527 
16528 				if (fill) {
16529 					caretContainer.appendChild(ed.getDoc().createTextNode(INVISIBLE_CHAR));
16530 				}
16531 
16532 				return caretContainer;
16533 			};
16534 
16535 			function isCaretContainerEmpty(node, nodes) {
16536 				while (node) {
16537 					if ((node.nodeType === 3 && node.nodeValue !== INVISIBLE_CHAR) || node.childNodes.length > 1) {
16538 						return false;
16539 					}
16540 
16541 					// Collect nodes
16542 					if (nodes && node.nodeType === 1) {
16543 						nodes.push(node);
16544 					}
16545 
16546 					node = node.firstChild;
16547 				}
16548 
16549 				return true;
16550 			};
16551 			
16552 			// Returns any parent caret container element
16553 			function getParentCaretContainer(node) {
16554 				while (node) {
16555 					if (node.id === caretContainerId) {
16556 						return node;
16557 					}
16558 
16559 					node = node.parentNode;
16560 				}
16561 			};
16562 
16563 			// Finds the first text node in the specified node
16564 			function findFirstTextNode(node) {
16565 				var walker;
16566 
16567 				if (node) {
16568 					walker = new TreeWalker(node, node);
16569 
16570 					for (node = walker.current(); node; node = walker.next()) {
16571 						if (node.nodeType === 3) {
16572 							return node;
16573 						}
16574 					}
16575 				}
16576 			};
16577 
16578 			// Removes the caret container for the specified node or all on the current document
16579 			function removeCaretContainer(node, move_caret) {
16580 				var child, rng;
16581 
16582 				if (!node) {
16583 					node = getParentCaretContainer(selection.getStart());
16584 
16585 					if (!node) {
16586 						while (node = dom.get(caretContainerId)) {
16587 							removeCaretContainer(node, false);
16588 						}
16589 					}
16590 				} else {
16591 					rng = selection.getRng(true);
16592 
16593 					if (isCaretContainerEmpty(node)) {
16594 						if (move_caret !== false) {
16595 							rng.setStartBefore(node);
16596 							rng.setEndBefore(node);
16597 						}
16598 
16599 						dom.remove(node);
16600 					} else {
16601 						child = findFirstTextNode(node);
16602 
16603 						if (child.nodeValue.charAt(0) === INVISIBLE_CHAR) {
16604 							child = child.deleteData(0, 1);
16605 						}
16606 
16607 						dom.remove(node, 1);
16608 					}
16609 
16610 					selection.setRng(rng);
16611 				}
16612 			};
16613 			
16614 			// Applies formatting to the caret postion
16615 			function applyCaretFormat() {
16616 				var rng, caretContainer, textNode, offset, bookmark, container, text;
16617 
16618 				rng = selection.getRng(true);
16619 				offset = rng.startOffset;
16620 				container = rng.startContainer;
16621 				text = container.nodeValue;
16622 
16623 				caretContainer = getParentCaretContainer(selection.getStart());
16624 				if (caretContainer) {
16625 					textNode = findFirstTextNode(caretContainer);
16626 				}
16627 
16628 				// Expand to word is caret is in the middle of a text node and the char before/after is a alpha numeric character
16629 				if (text && offset > 0 && offset < text.length && /\w/.test(text.charAt(offset)) && /\w/.test(text.charAt(offset - 1))) {
16630 					// Get bookmark of caret position
16631 					bookmark = selection.getBookmark();
16632 
16633 					// Collapse bookmark range (WebKit)
16634 					rng.collapse(true);
16635 
16636 					// Expand the range to the closest word and split it at those points
16637 					rng = expandRng(rng, get(name));
16638 					rng = rangeUtils.split(rng);
16639 
16640 					// Apply the format to the range
16641 					apply(name, vars, rng);
16642 
16643 					// Move selection back to caret position
16644 					selection.moveToBookmark(bookmark);
16645 				} else {
16646 					if (!caretContainer || textNode.nodeValue !== INVISIBLE_CHAR) {
16647 						caretContainer = createCaretContainer(true);
16648 						textNode = caretContainer.firstChild;
16649 
16650 						rng.insertNode(caretContainer);
16651 						offset = 1;
16652 
16653 						apply(name, vars, caretContainer);
16654 					} else {
16655 						apply(name, vars, caretContainer);
16656 					}
16657 
16658 					// Move selection to text node
16659 					selection.setCursorLocation(textNode, offset);
16660 				}
16661 			};
16662 
16663 			function removeCaretFormat() {
16664 				var rng = selection.getRng(true), container, offset, bookmark,
16665 					hasContentAfter, node, formatNode, parents = [], i, caretContainer;
16666 
16667 				container = rng.startContainer;
16668 				offset = rng.startOffset;
16669 				node = container;
16670 
16671 				if (container.nodeType == 3) {
16672 					if (offset != container.nodeValue.length || container.nodeValue === INVISIBLE_CHAR) {
16673 						hasContentAfter = true;
16674 					}
16675 
16676 					node = node.parentNode;
16677 				}
16678 
16679 				while (node) {
16680 					if (matchNode(node, name, vars)) {
16681 						formatNode = node;
16682 						break;
16683 					}
16684 
16685 					if (node.nextSibling) {
16686 						hasContentAfter = true;
16687 					}
16688 
16689 					parents.push(node);
16690 					node = node.parentNode;
16691 				}
16692 
16693 				// Node doesn't have the specified format
16694 				if (!formatNode) {
16695 					return;
16696 				}
16697 
16698 				// Is there contents after the caret then remove the format on the element
16699 				if (hasContentAfter) {
16700 					// Get bookmark of caret position
16701 					bookmark = selection.getBookmark();
16702 
16703 					// Collapse bookmark range (WebKit)
16704 					rng.collapse(true);
16705 
16706 					// Expand the range to the closest word and split it at those points
16707 					rng = expandRng(rng, get(name), true);
16708 					rng = rangeUtils.split(rng);
16709 
16710 					// Remove the format from the range
16711 					remove(name, vars, rng);
16712 
16713 					// Move selection back to caret position
16714 					selection.moveToBookmark(bookmark);
16715 				} else {
16716 					caretContainer = createCaretContainer();
16717 
16718 					node = caretContainer;
16719 					for (i = parents.length - 1; i >= 0; i--) {
16720 						node.appendChild(dom.clone(parents[i], false));
16721 						node = node.firstChild;
16722 					}
16723 
16724 					// Insert invisible character into inner most format element
16725 					node.appendChild(dom.doc.createTextNode(INVISIBLE_CHAR));
16726 					node = node.firstChild;
16727 
16728 					// Insert caret container after the formated node
16729 					dom.insertAfter(caretContainer, formatNode);
16730 
16731 					// Move selection to text node
16732 					selection.setCursorLocation(node, 1);
16733 				}
16734 			};
16735 
16736 			// Checks if the parent caret container node isn't empty if that is the case it
16737 			// will remove the bogus state on all children that isn't empty
16738 			function unmarkBogusCaretParents() {
16739 				var i, caretContainer, node;
16740 
16741 				caretContainer = getParentCaretContainer(selection.getStart());
16742 				if (caretContainer && !dom.isEmpty(caretContainer)) {
16743 					tinymce.walk(caretContainer, function(node) {
16744 						if (node.nodeType == 1 && node.id !== caretContainerId && !dom.isEmpty(node)) {
16745 							dom.setAttrib(node, 'data-mce-bogus', null);
16746 						}
16747 					}, 'childNodes');
16748 				}
16749 			};
16750 
16751 			// Only bind the caret events once
16752 			if (!self._hasCaretEvents) {
16753 				// Mark current caret container elements as bogus when getting the contents so we don't end up with empty elements
16754 				ed.onBeforeGetContent.addToTop(function() {
16755 					var nodes = [], i;
16756 
16757 					if (isCaretContainerEmpty(getParentCaretContainer(selection.getStart()), nodes)) {
16758 						// Mark children
16759 						i = nodes.length;
16760 						while (i--) {
16761 							dom.setAttrib(nodes[i], 'data-mce-bogus', '1');
16762 						}
16763 					}
16764 				});
16765 
16766 				// Remove caret container on mouse up and on key up
16767 				tinymce.each('onMouseUp onKeyUp'.split(' '), function(name) {
16768 					ed[name].addToTop(function() {
16769 						removeCaretContainer();
16770 						unmarkBogusCaretParents();
16771 					});
16772 				});
16773 
16774 				// Remove caret container on keydown and it's a backspace, enter or left/right arrow keys
16775 				ed.onKeyDown.addToTop(function(ed, e) {
16776 					var keyCode = e.keyCode;
16777 
16778 					if (keyCode == 8 || keyCode == 37 || keyCode == 39) {
16779 						removeCaretContainer(getParentCaretContainer(selection.getStart()));
16780 					}
16781 
16782 					unmarkBogusCaretParents();
16783 				});
16784 
16785 				// Remove bogus state if they got filled by contents using editor.selection.setContent
16786 				selection.onSetContent.add(unmarkBogusCaretParents);
16787 
16788 				self._hasCaretEvents = true;
16789 			}
16790 
16791 			// Do apply or remove caret format
16792 			if (type == "apply") {
16793 				applyCaretFormat();
16794 			} else {
16795 				removeCaretFormat();
16796 			}
16797 		};
16798 
16799 		function moveStart(rng) {
16800 			var container = rng.startContainer,
16801 					offset = rng.startOffset, isAtEndOfText,
16802 					walker, node, nodes, tmpNode;
16803 
16804 			// Convert text node into index if possible
16805 			if (container.nodeType == 3 && offset >= container.nodeValue.length) {
16806 				// Get the parent container location and walk from there
16807 				offset = nodeIndex(container);
16808 				container = container.parentNode;
16809 				isAtEndOfText = true;
16810 			}
16811 
16812 			// Move startContainer/startOffset in to a suitable node
16813 			if (container.nodeType == 1) {
16814 				nodes = container.childNodes;
16815 				container = nodes[Math.min(offset, nodes.length - 1)];
16816 				walker = new TreeWalker(container, dom.getParent(container, dom.isBlock));
16817 
16818 				// If offset is at end of the parent node walk to the next one
16819 				if (offset > nodes.length - 1 || isAtEndOfText)
16820 					walker.next();
16821 
16822 				for (node = walker.current(); node; node = walker.next()) {
16823 					if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {
16824 						// IE has a "neat" feature where it moves the start node into the closest element
16825 						// we can avoid this by inserting an element before it and then remove it after we set the selection
16826 						tmpNode = dom.create('a', null, INVISIBLE_CHAR);
16827 						node.parentNode.insertBefore(tmpNode, node);
16828 
16829 						// Set selection and remove tmpNode
16830 						rng.setStart(node, 0);
16831 						selection.setRng(rng);
16832 						dom.remove(tmpNode);
16833 
16834 						return;
16835 					}
16836 				}
16837 			}
16838 		};
16839 	};
16840 })(tinymce);
16841 
16842 tinymce.onAddEditor.add(function(tinymce, ed) {
16843 	var filters, fontSizes, dom, settings = ed.settings;
16844 
16845 	function replaceWithSpan(node, styles) {
16846 		tinymce.each(styles, function(value, name) {
16847 			if (value)
16848 				dom.setStyle(node, name, value);
16849 		});
16850 
16851 		dom.rename(node, 'span');
16852 	};
16853 
16854 	function convert(editor, params) {
16855 		dom = editor.dom;
16856 
16857 		if (settings.convert_fonts_to_spans) {
16858 			tinymce.each(dom.select('font,u,strike', params.node), function(node) {
16859 				filters[node.nodeName.toLowerCase()](ed.dom, node);
16860 			});
16861 		}
16862 	};
16863 
16864 	if (settings.inline_styles) {
16865 		fontSizes = tinymce.explode(settings.font_size_legacy_values);
16866 
16867 		filters = {
16868 			font : function(dom, node) {
16869 				replaceWithSpan(node, {
16870 					backgroundColor : node.style.backgroundColor,
16871 					color : node.color,
16872 					fontFamily : node.face,
16873 					fontSize : fontSizes[parseInt(node.size, 10) - 1]
16874 				});
16875 			},
16876 
16877 			u : function(dom, node) {
16878 				replaceWithSpan(node, {
16879 					textDecoration : 'underline'
16880 				});
16881 			},
16882 
16883 			strike : function(dom, node) {
16884 				replaceWithSpan(node, {
16885 					textDecoration : 'line-through'
16886 				});
16887 			}
16888 		};
16889 
16890 		ed.onPreProcess.add(convert);
16891 		ed.onSetContent.add(convert);
16892 
16893 		ed.onInit.add(function() {
16894 			ed.selection.onSetContent.add(convert);
16895 		});
16896 	}
16897 });
16898 
16899 (function(tinymce) {
16900 	var TreeWalker = tinymce.dom.TreeWalker;
16901 
16902 	tinymce.EnterKey = function(editor) {
16903 		var dom = editor.dom, selection = editor.selection, settings = editor.settings, undoManager = editor.undoManager, nonEmptyElementsMap = editor.schema.getNonEmptyElements();
16904 
16905 		function handleEnterKey(evt) {
16906 			var rng = selection.getRng(true), tmpRng, editableRoot, container, offset, parentBlock, documentMode,
16907 				newBlock, fragment, containerBlock, parentBlockName, containerBlockName, newBlockName, isAfterLastNodeInContainer;
16908 
16909 			// Returns true if the block can be split into two blocks or not
16910 			function canSplitBlock(node) {
16911 				return node &&
16912 					dom.isBlock(node) &&
16913 					!/^(TD|TH|CAPTION|FORM)$/.test(node.nodeName) &&
16914 					!/^(fixed|absolute)/i.test(node.style.position) && 
16915 					dom.getContentEditable(node) !== "true";
16916 			};
16917 
16918 			// Renders empty block on IE
16919 			function renderBlockOnIE(block) {
16920 				var oldRng;
16921 
16922 				if (tinymce.isIE && dom.isBlock(block)) {
16923 					oldRng = selection.getRng();
16924 					block.appendChild(dom.create('span', null, '\u00a0'));
16925 					selection.select(block);
16926 					block.lastChild.outerHTML = '';
16927 					selection.setRng(oldRng);
16928 				}
16929 			};
16930 
16931 			// Remove the first empty inline element of the block so this: <p><b><em></em></b>x</p> becomes this: <p>x</p>
16932 			function trimInlineElementsOnLeftSideOfBlock(block) {
16933 				var node = block, firstChilds = [], i;
16934 
16935 				// Find inner most first child ex: <p><i><b>*</b></i></p>
16936 				while (node = node.firstChild) {
16937 					if (dom.isBlock(node)) {
16938 						return;
16939 					}
16940 
16941 					if (node.nodeType == 1 && !nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
16942 						firstChilds.push(node);
16943 					}
16944 				}
16945 
16946 				i = firstChilds.length;
16947 				while (i--) {
16948 					node = firstChilds[i];
16949 					if (!node.hasChildNodes() || (node.firstChild == node.lastChild && node.firstChild.nodeValue === '')) {
16950 						dom.remove(node);
16951 					}
16952 				}
16953 			};
16954 			
16955 			// Moves the caret to a suitable position within the root for example in the first non pure whitespace text node or before an image
16956 			function moveToCaretPosition(root) {
16957 				var walker, node, rng, y, viewPort, lastNode = root, tempElm;
16958 
16959 				rng = dom.createRng();
16960 
16961 				if (root.hasChildNodes()) {
16962 					walker = new TreeWalker(root, root);
16963 
16964 					while (node = walker.current()) {
16965 						if (node.nodeType == 3) {
16966 							rng.setStart(node, 0);
16967 							rng.setEnd(node, 0);
16968 							break;
16969 						}
16970 
16971 						if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
16972 							rng.setStartBefore(node);
16973 							rng.setEndBefore(node);
16974 							break;
16975 						}
16976 
16977 						lastNode = node;
16978 						node = walker.next();
16979 					}
16980 
16981 					if (!node) {
16982 						rng.setStart(lastNode, 0);
16983 						rng.setEnd(lastNode, 0);
16984 					}
16985 				} else {
16986 					if (root.nodeName == 'BR') {
16987 						if (root.nextSibling && dom.isBlock(root.nextSibling)) {
16988 							// Trick on older IE versions to render the caret before the BR between two lists
16989 							if (!documentMode || documentMode < 9) {
16990 								tempElm = dom.create('br');
16991 								root.parentNode.insertBefore(tempElm, root);
16992 							}
16993 
16994 							rng.setStartBefore(root);
16995 							rng.setEndBefore(root);
16996 						} else {
16997 							rng.setStartAfter(root);
16998 							rng.setEndAfter(root);
16999 						}
17000 					} else {
17001 						rng.setStart(root, 0);
17002 						rng.setEnd(root, 0);
17003 					}
17004 				}
17005 
17006 				selection.setRng(rng);
17007 
17008 				// Remove tempElm created for old IE:s
17009 				dom.remove(tempElm);
17010 
17011 				viewPort = dom.getViewPort(editor.getWin());
17012 
17013 				// scrollIntoView seems to scroll the parent window in most browsers now including FF 3.0b4 so it's time to stop using it and do it our selfs
17014 				y = dom.getPos(root).y;
17015 				if (y < viewPort.y || y + 25 > viewPort.y + viewPort.h) {
17016 					editor.getWin().scrollTo(0, y < viewPort.y ? y : y - viewPort.h + 25); // Needs to be hardcoded to roughly one line of text if a huge text block is broken into two blocks
17017 				}
17018 			};
17019 
17020 			// Creates a new block element by cloning the current one or creating a new one if the name is specified
17021 			// This function will also copy any text formatting from the parent block and add it to the new one
17022 			function createNewBlock(name) {
17023 				var node = container, block, clonedNode, caretNode;
17024 
17025 				block = name || parentBlockName == "TABLE" ? dom.create(name || newBlockName) : parentBlock.cloneNode(false);
17026 				caretNode = block;
17027 
17028 				// Clone any parent styles
17029 				if (settings.keep_styles !== false) {
17030 					do {
17031 						if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(node.nodeName)) {
17032 							clonedNode = node.cloneNode(false);
17033 							dom.setAttrib(clonedNode, 'id', ''); // Remove ID since it needs to be document unique
17034 
17035 							if (block.hasChildNodes()) {
17036 								clonedNode.appendChild(block.firstChild);
17037 								block.appendChild(clonedNode);
17038 							} else {
17039 								caretNode = clonedNode;
17040 								block.appendChild(clonedNode);
17041 							}
17042 						}
17043 					} while (node = node.parentNode);
17044 				}
17045 
17046 				// BR is needed in empty blocks on non IE browsers
17047 				if (!tinymce.isIE) {
17048 					caretNode.innerHTML = '<br>';
17049 				}
17050 
17051 				return block;
17052 			};
17053 
17054 			// Returns true/false if the caret is at the start/end of the parent block element
17055 			function isCaretAtStartOrEndOfBlock(start) {
17056 				var walker, node, name;
17057 
17058 				// Caret is in the middle of a text node like "a|b"
17059 				if (container.nodeType == 3 && (start ? offset > 0 : offset < container.nodeValue.length)) {
17060 					return false;
17061 				}
17062 
17063 				// If after the last element in block node edge case for #5091
17064 				if (container.parentNode == parentBlock && isAfterLastNodeInContainer && !start) {
17065 					return true;
17066 				}
17067 
17068 				// If the caret if before the first element in parentBlock
17069 				if (start && container.nodeType == 1 && container == parentBlock.firstChild) {
17070 					return true;
17071 				}
17072 
17073 				// Caret can be before/after a table
17074 				if (container.nodeName === "TABLE" || (container.previousSibling && container.previousSibling.nodeName == "TABLE")) {
17075 					return (isAfterLastNodeInContainer && !start) || (!isAfterLastNodeInContainer && start);
17076 				}
17077 
17078 				// Walk the DOM and look for text nodes or non empty elements
17079 				walker = new TreeWalker(container, parentBlock);
17080 	
17081 				// If caret is in beginning or end of a text block then jump to the next/previous node
17082 				if (container.nodeType == 3) {
17083 					if (start && offset == 0) {
17084 						walker.prev();
17085 					} else if (!start && offset == container.nodeValue.length) {
17086 						walker.next();
17087 					}
17088 				}
17089 
17090 				while (node = walker.current()) {
17091 					if (node.nodeType === 1) {
17092 						// Ignore bogus elements
17093 						if (!node.getAttribute('data-mce-bogus')) {
17094 							// Keep empty elements like <img /> <input /> but not trailing br:s like <p>text|<br></p>
17095 							name = node.nodeName.toLowerCase();
17096 							if (nonEmptyElementsMap[name] && name !== 'br') {
17097 								return false;
17098 							}
17099 						}
17100 					} else if (node.nodeType === 3 && !/^[ \t\r\n]*$/.test(node.nodeValue)) {
17101 						return false;
17102 					}
17103 
17104 					if (start) {
17105 						walker.prev();
17106 					} else {
17107 						walker.next();
17108 					}
17109 				}
17110 
17111 				return true;
17112 			};
17113 
17114 			// Wraps any text nodes or inline elements in the specified forced root block name
17115 			function wrapSelfAndSiblingsInDefaultBlock(container, offset) {
17116 				var newBlock, parentBlock, startNode, node, next, blockName = newBlockName || 'P';
17117 
17118 				// Not in a block element or in a table cell or caption
17119 				parentBlock = dom.getParent(container, dom.isBlock);
17120 				if (!parentBlock || !canSplitBlock(parentBlock)) {
17121 					parentBlock = parentBlock || editableRoot;
17122 
17123 					if (!parentBlock.hasChildNodes()) {
17124 						newBlock = dom.create(blockName);
17125 						parentBlock.appendChild(newBlock);
17126 						rng.setStart(newBlock, 0);
17127 						rng.setEnd(newBlock, 0);
17128 						return newBlock;
17129 					}
17130 
17131 					// Find parent that is the first child of parentBlock
17132 					node = container;
17133 					while (node.parentNode != parentBlock) {
17134 						node = node.parentNode;
17135 					}
17136 
17137 					// Loop left to find start node start wrapping at
17138 					while (node && !dom.isBlock(node)) {
17139 						startNode = node;
17140 						node = node.previousSibling;
17141 					}
17142 
17143 					if (startNode) {
17144 						newBlock = dom.create(blockName);
17145 						startNode.parentNode.insertBefore(newBlock, startNode);
17146 
17147 						// Start wrapping until we hit a block
17148 						node = startNode;
17149 						while (node && !dom.isBlock(node)) {
17150 							next = node.nextSibling;
17151 							newBlock.appendChild(node);
17152 							node = next;
17153 						}
17154 
17155 						// Restore range to it's past location
17156 						rng.setStart(container, offset);
17157 						rng.setEnd(container, offset);
17158 					}
17159 				}
17160 
17161 				return container;
17162 			};
17163 
17164 			// Inserts a block or br before/after or in the middle of a split list of the LI is empty
17165 			function handleEmptyListItem() {
17166 				function isFirstOrLastLi(first) {
17167 					var node = containerBlock[first ? 'firstChild' : 'lastChild'];
17168 
17169 					// Find first/last element since there might be whitespace there
17170 					while (node) {
17171 						if (node.nodeType == 1) {
17172 							break;
17173 						}
17174 
17175 						node = node[first ? 'nextSibling' : 'previousSibling'];
17176 					}
17177 
17178 					return node === parentBlock;
17179 				};
17180 
17181 				newBlock = newBlockName ? createNewBlock(newBlockName) : dom.create('BR');
17182 
17183 				if (isFirstOrLastLi(true) && isFirstOrLastLi()) {
17184 					// Is first and last list item then replace the OL/UL with a text block
17185 					dom.replace(newBlock, containerBlock);
17186 				} else if (isFirstOrLastLi(true)) {
17187 					// First LI in list then remove LI and add text block before list
17188 					containerBlock.parentNode.insertBefore(newBlock, containerBlock);
17189 				} else if (isFirstOrLastLi()) {
17190 					// Last LI in list then temove LI and add text block after list
17191 					dom.insertAfter(newBlock, containerBlock);
17192 					renderBlockOnIE(newBlock);
17193 				} else {
17194 					// Middle LI in list the split the list and insert a text block in the middle
17195 					// Extract after fragment and insert it after the current block
17196 					tmpRng = rng.cloneRange();
17197 					tmpRng.setStartAfter(parentBlock);
17198 					tmpRng.setEndAfter(containerBlock);
17199 					fragment = tmpRng.extractContents();
17200 					dom.insertAfter(fragment, containerBlock);
17201 					dom.insertAfter(newBlock, containerBlock);
17202 				}
17203 
17204 				dom.remove(parentBlock);
17205 				moveToCaretPosition(newBlock);
17206 				undoManager.add();
17207 			};
17208 
17209 			// Walks the parent block to the right and look for BR elements
17210 			function hasRightSideBr() {
17211 				var walker = new TreeWalker(container, parentBlock), node;
17212 
17213 				while (node = walker.current()) {
17214 					if (node.nodeName == 'BR') {
17215 						return true;
17216 					}
17217 
17218 					node = walker.next();
17219 				}
17220 			}
17221 			
17222 			// Inserts a BR element if the forced_root_block option is set to false or empty string
17223 			function insertBr() {
17224 				var brElm, extraBr;
17225 
17226 				if (container && container.nodeType == 3 && offset >= container.nodeValue.length) {
17227 					// Insert extra BR element at the end block elements
17228 					if (!tinymce.isIE && !hasRightSideBr()) {
17229 						brElm = dom.create('br')
17230 						rng.insertNode(brElm);
17231 						rng.setStartAfter(brElm);
17232 						rng.setEndAfter(brElm);
17233 						extraBr = true;
17234 					}
17235 				}
17236 
17237 				brElm = dom.create('br');
17238 				rng.insertNode(brElm);
17239 
17240 				// Rendering modes below IE8 doesn't display BR elements in PRE unless we have a \n before it
17241 				if (tinymce.isIE && parentBlockName == 'PRE' && (!documentMode || documentMode < 8)) {
17242 					brElm.parentNode.insertBefore(dom.doc.createTextNode('\r'), brElm);
17243 				}
17244 
17245 				if (!extraBr) {
17246 					rng.setStartAfter(brElm);
17247 					rng.setEndAfter(brElm);
17248 				} else {
17249 					rng.setStartBefore(brElm);
17250 					rng.setEndBefore(brElm);
17251 				}
17252 
17253 				selection.setRng(rng);
17254 				undoManager.add();
17255 			};
17256 
17257 			// Trims any linebreaks at the beginning of node user for example when pressing enter in a PRE element
17258 			function trimLeadingLineBreaks(node) {
17259 				do {
17260 					if (node.nodeType === 3) {
17261 						node.nodeValue = node.nodeValue.replace(/^[\r\n]+/, '');
17262 					}
17263 
17264 					node = node.firstChild;
17265 				} while (node);
17266 			};
17267 
17268 			function getEditableRoot(node) {
17269 				var root = dom.getRoot(), parent, editableRoot;
17270 
17271 				// Get all parents until we hit a non editable parent or the root
17272 				parent = node;
17273 				while (parent !== root && dom.getContentEditable(parent) !== "false") {
17274 					if (dom.getContentEditable(parent) === "true") {
17275 						editableRoot = parent;
17276 					}
17277 
17278 					parent = parent.parentNode;
17279 				}
17280 				
17281 				return parent !== root ? editableRoot : root;
17282 			};
17283 
17284 			// Adds a BR at the end of blocks that only contains an IMG or INPUT since these might be floated and then they won't expand the block
17285 			function addBrToBlockIfNeeded(block) {
17286 				var lastChild;
17287 
17288 				// IE will render the blocks correctly other browsers needs a BR
17289 				if (!tinymce.isIE) {
17290 					block.normalize(); // Remove empty text nodes that got left behind by the extract
17291 
17292 					// Check if the block is empty or contains a floated last child
17293 					lastChild = block.lastChild;
17294 					if (!lastChild || (/^(left|right)$/gi.test(dom.getStyle(lastChild, 'float', true)))) {
17295 						dom.add(block, 'br');
17296 					}
17297 				}
17298 			};
17299 
17300 			// Delete any selected contents
17301 			if (!rng.collapsed) {
17302 				editor.execCommand('Delete');
17303 				return;
17304 			}
17305 
17306 			// Event is blocked by some other handler for example the lists plugin
17307 			if (evt.isDefaultPrevented()) {
17308 				return;
17309 			}
17310 
17311 			// Setup range items and newBlockName
17312 			container = rng.startContainer;
17313 			offset = rng.startOffset;
17314 			newBlockName = settings.forced_root_block;
17315 			newBlockName = newBlockName ? newBlockName.toUpperCase() : '';
17316 			documentMode = dom.doc.documentMode;
17317 
17318 			// Resolve node index
17319 			if (container.nodeType == 1 && container.hasChildNodes()) {
17320 				isAfterLastNodeInContainer = offset > container.childNodes.length - 1;
17321 				container = container.childNodes[Math.min(offset, container.childNodes.length - 1)] || container;
17322 				if (isAfterLastNodeInContainer && container.nodeType == 3) {
17323 					offset = container.nodeValue.length;
17324 				} else {
17325 					offset = 0;
17326 				}
17327 			}
17328 
17329 			// Get editable root node normaly the body element but sometimes a div or span
17330 			editableRoot = getEditableRoot(container);
17331 
17332 			// If there is no editable root then enter is done inside a contentEditable false element
17333 			if (!editableRoot) {
17334 				return;
17335 			}
17336 
17337 			undoManager.beforeChange();
17338 
17339 			// If editable root isn't block nor the root of the editor
17340 			if (!dom.isBlock(editableRoot) && editableRoot != dom.getRoot()) {
17341 				if (!newBlockName || evt.shiftKey) {
17342 					insertBr();
17343 				}
17344 
17345 				return;
17346 			}
17347 
17348 			// Wrap the current node and it's sibling in a default block if it's needed.
17349 			// for example this <td>text|<b>text2</b></td> will become this <td><p>text|<b>text2</p></b></td>
17350 			// This won't happen if root blocks are disabled or the shiftKey is pressed
17351 			if ((newBlockName && !evt.shiftKey) || (!newBlockName && evt.shiftKey)) {
17352 				container = wrapSelfAndSiblingsInDefaultBlock(container, offset);
17353 			}
17354 
17355 			// Find parent block and setup empty block paddings
17356 			parentBlock = dom.getParent(container, dom.isBlock);
17357 			containerBlock = parentBlock ? dom.getParent(parentBlock.parentNode, dom.isBlock) : null;
17358 
17359 			// Setup block names
17360 			parentBlockName = parentBlock ? parentBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
17361 			containerBlockName = containerBlock ? containerBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
17362 
17363 			// Handle enter inside an empty list item
17364 			if (parentBlockName == 'LI' && dom.isEmpty(parentBlock)) {
17365 				// Let the list plugin or browser handle nested lists for now
17366 				if (/^(UL|OL|LI)$/.test(containerBlock.parentNode.nodeName)) {
17367 					return false;
17368 				}
17369 
17370 				handleEmptyListItem();
17371 				return;
17372 			}
17373 
17374 			// Don't split PRE tags but insert a BR instead easier when writing code samples etc
17375 			if (parentBlockName == 'PRE' && settings.br_in_pre !== false) {
17376 				if (!evt.shiftKey) {
17377 					insertBr();
17378 					return;
17379 				}
17380 			} else {
17381 				// If no root block is configured then insert a BR by default or if the shiftKey is pressed
17382 				if ((!newBlockName && !evt.shiftKey && parentBlockName != 'LI') || (newBlockName && evt.shiftKey)) {
17383 					insertBr();
17384 					return;
17385 				}
17386 			}
17387 
17388 			// Default block name if it's not configured
17389 			newBlockName = newBlockName || 'P';
17390 
17391 			// Insert new block before/after the parent block depending on caret location
17392 			if (isCaretAtStartOrEndOfBlock()) {
17393 				// If the caret is at the end of a header we produce a P tag after it similar to Word unless we are in a hgroup
17394 				if (/^(H[1-6]|PRE)$/.test(parentBlockName) && containerBlockName != 'HGROUP') {
17395 					newBlock = createNewBlock(newBlockName);
17396 				} else {
17397 					newBlock = createNewBlock();
17398 				}
17399 
17400 				// Split the current container block element if enter is pressed inside an empty inner block element
17401 				if (settings.end_container_on_empty_block && canSplitBlock(containerBlock) && dom.isEmpty(parentBlock)) {
17402 					// Split container block for example a BLOCKQUOTE at the current blockParent location for example a P
17403 					newBlock = dom.split(containerBlock, parentBlock);
17404 				} else {
17405 					dom.insertAfter(newBlock, parentBlock);
17406 				}
17407 
17408 				moveToCaretPosition(newBlock);
17409 			} else if (isCaretAtStartOrEndOfBlock(true)) {
17410 				// Insert new block before
17411 				newBlock = parentBlock.parentNode.insertBefore(createNewBlock(), parentBlock);
17412 				renderBlockOnIE(newBlock);
17413 			} else {
17414 				// Extract after fragment and insert it after the current block
17415 				tmpRng = rng.cloneRange();
17416 				tmpRng.setEndAfter(parentBlock);
17417 				fragment = tmpRng.extractContents();
17418 				trimLeadingLineBreaks(fragment);
17419 				newBlock = fragment.firstChild;
17420 				dom.insertAfter(fragment, parentBlock);
17421 				trimInlineElementsOnLeftSideOfBlock(newBlock);
17422 				addBrToBlockIfNeeded(parentBlock);
17423 				moveToCaretPosition(newBlock);
17424 			}
17425 
17426 			dom.setAttrib(newBlock, 'id', ''); // Remove ID since it needs to be document unique
17427 			undoManager.add();
17428 		}
17429 
17430 		editor.onKeyDown.add(function(ed, evt) {
17431 			if (evt.keyCode == 13) {
17432 				if (handleEnterKey(evt) !== false) {
17433 					evt.preventDefault();
17434 				}
17435 			}
17436 		});
17437 	};
17438 })(tinymce);
17439 
17440